зеркало из https://github.com/github/ruby.git
Removed WEBrick and that tests
We can handle uri, time and others without `make test-all` dependencies now.
This commit is contained in:
Родитель
bfba96a106
Коммит
9a5e3a4007
|
@ -1,232 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
##
|
||||
# = 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 server 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.
|
||||
#
|
||||
# == Security
|
||||
#
|
||||
# *Warning:* WEBrick is not recommended for production. It only implements
|
||||
# basic security checks.
|
||||
#
|
||||
# == 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 +server.mount_proc+ must precede +server.start+.
|
||||
#
|
||||
# == 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
|
||||
#
|
||||
# See WEBrick::HTTPProxy for further details including modifying proxied
|
||||
# responses.
|
||||
#
|
||||
# == 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 daemonized Web Server
|
||||
#
|
||||
# WEBrick can be run as a daemonized 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_relative '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'
|
|
@ -1,6 +0,0 @@
|
|||
# Add files to this as they become documented
|
||||
|
||||
*.rb
|
||||
|
||||
httpauth
|
||||
httpservlet
|
|
@ -1,157 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# 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 received
|
||||
# %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
|
||||
|
||||
##
|
||||
# Formats +params+ according to +format_string+ which is described in
|
||||
# setup_params.
|
||||
|
||||
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
|
||||
|
||||
##
|
||||
# Escapes control characters in +data+
|
||||
|
||||
def escape(data)
|
||||
data = data.gsub(/[[:cntrl:]\\]+/) {$&.dump[1...-1]}
|
||||
data.untaint if RUBY_VERSION < '2.7'
|
||||
data
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,313 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# 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_relative "httprequest"
|
||||
require_relative "httpresponse"
|
||||
require_relative "config"
|
||||
require "stringio"
|
||||
|
||||
module WEBrick
|
||||
|
||||
# A CGI library using WEBrick requests and responses.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# class MyCGI < WEBrick::CGI
|
||||
# def do_GET req, res
|
||||
# res.body = 'it worked!'
|
||||
# res.status = 200
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# MyCGI.new.start
|
||||
|
||||
class CGI
|
||||
|
||||
# The CGI error exception class
|
||||
|
||||
CGIError = Class.new(StandardError)
|
||||
|
||||
##
|
||||
# The CGI configuration. This is based on WEBrick::Config::HTTP
|
||||
|
||||
attr_reader :config
|
||||
|
||||
##
|
||||
# The CGI logger
|
||||
|
||||
attr_reader :logger
|
||||
|
||||
##
|
||||
# Creates a new CGI interface.
|
||||
#
|
||||
# The first argument in +args+ is a configuration hash which would update
|
||||
# WEBrick::Config::HTTP.
|
||||
#
|
||||
# Any remaining arguments are stored in the <code>@options</code> instance
|
||||
# variable for use by a subclass.
|
||||
|
||||
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
|
||||
|
||||
##
|
||||
# Reads +key+ from the configuration
|
||||
|
||||
def [](key)
|
||||
@config[key]
|
||||
end
|
||||
|
||||
##
|
||||
# Starts the CGI process with the given environment +env+ and standard
|
||||
# input and output +stdin+ and +stdout+.
|
||||
|
||||
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
|
||||
|
||||
##
|
||||
# Services the request +req+ which will fill in the response +res+. See
|
||||
# WEBrick::HTTPServlet::AbstractServlet#service for details.
|
||||
|
||||
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
|
||||
|
||||
##
|
||||
# Provides HTTP socket emulation from the CGI environment
|
||||
|
||||
class Socket # :nodoc:
|
||||
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 write(data)
|
||||
@out_port.write(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
|
|
@ -1,36 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# 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 compatibility.
|
||||
#
|
||||
# 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
|
|
@ -1,158 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# 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_relative 'version'
|
||||
require_relative 'httpversion'
|
||||
require_relative 'httputils'
|
||||
require_relative 'utils'
|
||||
require_relative 'log'
|
||||
|
||||
module WEBrick
|
||||
module Config
|
||||
LIBDIR = File::dirname(__FILE__) # :nodoc:
|
||||
|
||||
# for GenericServer
|
||||
General = Hash.new { |hash, key|
|
||||
case key
|
||||
when :ServerName
|
||||
hash[key] = Utils.getservername
|
||||
else
|
||||
nil
|
||||
end
|
||||
}.update(
|
||||
: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 => true,
|
||||
: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
|
||||
)
|
||||
|
||||
##
|
||||
# Default configuration for WEBrick::HTTPServlet::FileHandler
|
||||
#
|
||||
# :AcceptableLanguages::
|
||||
# Array of languages allowed for accept-language. There is no default
|
||||
# :DirectoryCallback::
|
||||
# Allows preprocessing of directory requests. There is no default
|
||||
# callback.
|
||||
# :FancyIndexing::
|
||||
# If true, show an index for directories. The default is true.
|
||||
# :FileCallback::
|
||||
# Allows preprocessing of file requests. There is no default callback.
|
||||
# :HandlerCallback::
|
||||
# Allows preprocessing of requests. There is no default callback.
|
||||
# :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. .ht* and *~ are
|
||||
# excluded by default.
|
||||
# :UserDir::
|
||||
# Directory inside ~user to serve content from for /~user requests.
|
||||
# Only works if mounted on /. Disabled by default.
|
||||
|
||||
FileHandler = {
|
||||
:NondisclosureName => [".ht*", "*~"],
|
||||
:FancyIndexing => false,
|
||||
:HandlerTable => {},
|
||||
:HandlerCallback => nil,
|
||||
:DirectoryCallback => nil,
|
||||
:FileCallback => nil,
|
||||
:UserDir => nil, # e.g. "public_html"
|
||||
:AcceptableLanguages => [] # ["en", "ja", ... ]
|
||||
}
|
||||
|
||||
##
|
||||
# Default configuration for WEBrick::HTTPAuth::BasicAuth
|
||||
#
|
||||
# :AutoReloadUserDB:: Reload the user database provided by :UserDB
|
||||
# automatically?
|
||||
|
||||
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
|
|
@ -1,172 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# 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_relative 'httputils'
|
||||
|
||||
module WEBrick
|
||||
|
||||
##
|
||||
# Processes HTTP cookies
|
||||
|
||||
class Cookie
|
||||
|
||||
##
|
||||
# The cookie name
|
||||
|
||||
attr_reader :name
|
||||
|
||||
##
|
||||
# The cookie value
|
||||
|
||||
attr_accessor :value
|
||||
|
||||
##
|
||||
# The cookie version
|
||||
|
||||
attr_accessor :version
|
||||
|
||||
##
|
||||
# The cookie domain
|
||||
attr_accessor :domain
|
||||
|
||||
##
|
||||
# The cookie path
|
||||
|
||||
attr_accessor :path
|
||||
|
||||
##
|
||||
# Is this a secure cookie?
|
||||
|
||||
attr_accessor :secure
|
||||
|
||||
##
|
||||
# The cookie comment
|
||||
|
||||
attr_accessor :comment
|
||||
|
||||
##
|
||||
# The maximum age of the cookie
|
||||
|
||||
attr_accessor :max_age
|
||||
|
||||
#attr_accessor :comment_url, :discard, :port
|
||||
|
||||
##
|
||||
# Creates a new cookie with the given +name+ and +value+
|
||||
|
||||
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
|
||||
|
||||
##
|
||||
# Sets the cookie expiration to the time +t+. The expiration time may be
|
||||
# a false value to disable expiration or a Time or HTTP format time string
|
||||
# to set the expiration date.
|
||||
|
||||
def expires=(t)
|
||||
@expires = t && (t.is_a?(Time) ? t.httpdate : t.to_s)
|
||||
end
|
||||
|
||||
##
|
||||
# Retrieves the expiration time as a Time
|
||||
|
||||
def expires
|
||||
@expires && Time.parse(@expires)
|
||||
end
|
||||
|
||||
##
|
||||
# The cookie string suitable for use in an HTTP header
|
||||
|
||||
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
|
||||
|
||||
##
|
||||
# Parses a Cookie field sent from the user-agent. Returns an array of
|
||||
# cookies.
|
||||
|
||||
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
|
||||
|
||||
##
|
||||
# Parses the cookie in +str+
|
||||
|
||||
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
|
||||
|
||||
##
|
||||
# Parses the cookies in +str+
|
||||
|
||||
def self.parse_set_cookies(str)
|
||||
return str.split(/,(?=[^;,]*=)|,$/).collect{|c|
|
||||
parse_set_cookie(c)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,30 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# 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)
|
||||
return "" unless string
|
||||
str = string.b
|
||||
str.gsub!(/&/n, '&')
|
||||
str.gsub!(/\"/n, '"')
|
||||
str.gsub!(/>/n, '>')
|
||||
str.gsub!(/</n, '<')
|
||||
str.force_encoding(string.encoding)
|
||||
end
|
||||
module_function :escape
|
||||
|
||||
end
|
||||
end
|
|
@ -1,96 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# 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_relative 'httpauth/basicauth'
|
||||
require_relative 'httpauth/digestauth'
|
||||
require_relative 'httpauth/htpasswd'
|
||||
require_relative 'httpauth/htdigest'
|
||||
require_relative '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
|
|
@ -1,117 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# 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" # :nodoc:
|
||||
ResponseField = "WWW-Authenticate" # :nodoc:
|
||||
ResponseInfoField = "Authentication-Info" # :nodoc:
|
||||
AuthException = HTTPStatus::Unauthorized # :nodoc:
|
||||
|
||||
##
|
||||
# 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
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
##
|
||||
# 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
|
||||
|
||||
# :startdoc:
|
||||
end
|
||||
|
||||
##
|
||||
# Module providing generic support for both Digest and Basic
|
||||
# authentication schemes for proxies.
|
||||
|
||||
module ProxyAuthenticator
|
||||
RequestField = "Proxy-Authorization" # :nodoc:
|
||||
ResponseField = "Proxy-Authenticate" # :nodoc:
|
||||
InfoField = "Proxy-Authentication-Info" # :nodoc:
|
||||
AuthException = HTTPStatus::ProxyAuthenticationRequired # :nodoc:
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,116 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# 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_relative '../config'
|
||||
require_relative '../httpstatus'
|
||||
require_relative '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', password_hash: :bcrypt
|
||||
# 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" # :nodoc:
|
||||
|
||||
##
|
||||
# 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
|
||||
|
||||
case encpass
|
||||
when /\A\$2[aby]\$/
|
||||
password_matches = BCrypt::Password.new(encpass.sub(/\A\$2[aby]\$/, '$2a$')) == password
|
||||
else
|
||||
password_matches = password.crypt(encpass) == encpass
|
||||
end
|
||||
|
||||
unless password_matches
|
||||
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 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
|
|
@ -1,395 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# 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_relative '../config'
|
||||
require_relative '../httpstatus'
|
||||
require_relative '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" # :nodoc:
|
||||
|
||||
##
|
||||
# Struct containing the opaque portion of the digest authentication
|
||||
|
||||
OpaqueInfo = Struct.new(:time, :nonce, :nc) # :nodoc:
|
||||
|
||||
##
|
||||
# Digest authentication algorithm
|
||||
|
||||
attr_reader :algorithm
|
||||
|
||||
##
|
||||
# Quality of protection. RFC 2617 defines "auth" and "auth-int"
|
||||
|
||||
attr_reader :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 = Thread::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 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
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
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 allowed.', 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"
|
||||
body_digest = @h.new
|
||||
req.body { |chunk| body_digest.update(chunk) }
|
||||
body_digest = body_digest.hexdigest
|
||||
ha2 = hexdigest(req.request_method, auth_req['uri'], body_digest)
|
||||
ha2_res = hexdigest("", auth_req['uri'], body_digest)
|
||||
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 = {}
|
||||
string.scan(/\G\s*([\w\-.*%!]+)=\s*(?:\"((?>\\.|[^\"])*)\"|([^,\"]*))\s*,?/) do
|
||||
ret[$1] = $3 || $2.gsub(/\\(.)/, "\\1")
|
||||
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("m0") # 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
|
||||
|
||||
# :startdoc:
|
||||
end
|
||||
|
||||
##
|
||||
# Digest authentication for proxy servers. See DigestAuth for details.
|
||||
|
||||
class ProxyDigestAuth < DigestAuth
|
||||
include ProxyAuthenticator
|
||||
|
||||
private
|
||||
def check_uri(req, auth_req) # :nodoc:
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,132 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# 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_relative 'userdb'
|
||||
require_relative '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 = Thread::Mutex::new
|
||||
@auth_type = DigestAuth
|
||||
File.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
|
||||
File.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.create("htpasswd", File::dirname(output))
|
||||
renamed = false
|
||||
begin
|
||||
each{|item| tmp.puts(item.join(":")) }
|
||||
tmp.close
|
||||
File::rename(tmp.path, output)
|
||||
renamed = true
|
||||
ensure
|
||||
tmp.close
|
||||
File.unlink(tmp.path) if !renamed
|
||||
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
|
|
@ -1,97 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# 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
|
||||
File.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
|
||||
File.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.create("htgroup", File::dirname(output))
|
||||
begin
|
||||
@group.keys.sort.each{|group|
|
||||
tmp.puts(format("%s: %s", group, self.members(group).join(" ")))
|
||||
}
|
||||
ensure
|
||||
tmp.close
|
||||
if $!
|
||||
File.unlink(tmp.path)
|
||||
else
|
||||
return File.rename(tmp.path, output)
|
||||
end
|
||||
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
|
|
@ -1,158 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# 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_relative 'userdb'
|
||||
require_relative '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, password_hash: nil)
|
||||
@path = path
|
||||
@mtime = Time.at(0)
|
||||
@passwd = Hash.new
|
||||
@auth_type = BasicAuth
|
||||
@password_hash = password_hash
|
||||
|
||||
case @password_hash
|
||||
when nil
|
||||
# begin
|
||||
# require "string/crypt"
|
||||
# rescue LoadError
|
||||
# warn("Unable to load string/crypt, proceeding with deprecated use of String#crypt, consider using password_hash: :bcrypt")
|
||||
# end
|
||||
@password_hash = :crypt
|
||||
when :crypt
|
||||
# require "string/crypt"
|
||||
when :bcrypt
|
||||
require "bcrypt"
|
||||
else
|
||||
raise ArgumentError, "only :crypt and :bcrypt are supported for password_hash keyword argument"
|
||||
end
|
||||
|
||||
File.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
|
||||
File.open(@path){|io|
|
||||
while line = io.gets
|
||||
line.chomp!
|
||||
case line
|
||||
when %r!\A[^:]+:[a-zA-Z0-9./]{13}\z!
|
||||
if @password_hash == :bcrypt
|
||||
raise StandardError, ".htpasswd file contains crypt password, only bcrypt passwords supported"
|
||||
end
|
||||
user, pass = line.split(":")
|
||||
when %r!\A[^:]+:\$2[aby]\$\d{2}\$.{53}\z!
|
||||
if @password_hash == :crypt
|
||||
raise StandardError, ".htpasswd file contains bcrypt password, only crypt passwords supported"
|
||||
end
|
||||
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.create("htpasswd", File::dirname(output))
|
||||
renamed = false
|
||||
begin
|
||||
each{|item| tmp.puts(item.join(":")) }
|
||||
tmp.close
|
||||
File::rename(tmp.path, output)
|
||||
renamed = true
|
||||
ensure
|
||||
tmp.close
|
||||
File.unlink(tmp.path) if !renamed
|
||||
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)
|
||||
if @password_hash == :bcrypt
|
||||
# Cost of 5 to match Apache default, and because the
|
||||
# bcrypt default of 10 will introduce significant delays
|
||||
# for every request.
|
||||
@passwd[user] = BCrypt::Password.create(pass, :cost=>5)
|
||||
else
|
||||
@passwd[user] = make_passwd(realm, user, pass)
|
||||
end
|
||||
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
|
|
@ -1,53 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# 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
|
|
@ -1,354 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# 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_relative "httpserver"
|
||||
require "net/http"
|
||||
|
||||
module WEBrick
|
||||
|
||||
NullReader = Object.new # :nodoc:
|
||||
class << NullReader # :nodoc:
|
||||
def read(*args)
|
||||
nil
|
||||
end
|
||||
alias gets read
|
||||
end
|
||||
|
||||
FakeProxyURI = Object.new # :nodoc:
|
||||
class << FakeProxyURI # :nodoc:
|
||||
def method_missing(meth, *args)
|
||||
if %w(scheme host port path query userinfo).member?(meth.to_s)
|
||||
return nil
|
||||
end
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# :startdoc:
|
||||
|
||||
##
|
||||
# An HTTP Proxy server which proxies GET, HEAD and POST requests.
|
||||
#
|
||||
# To create a simple proxy server:
|
||||
#
|
||||
# require 'webrick'
|
||||
# require 'webrick/httpproxy'
|
||||
#
|
||||
# proxy = WEBrick::HTTPProxyServer.new Port: 8000
|
||||
#
|
||||
# trap 'INT' do proxy.shutdown end
|
||||
# trap 'TERM' do proxy.shutdown end
|
||||
#
|
||||
# proxy.start
|
||||
#
|
||||
# See ::new for proxy-specific configuration items.
|
||||
#
|
||||
# == Modifying proxied responses
|
||||
#
|
||||
# To modify content the proxy server returns use the +:ProxyContentHandler+
|
||||
# option:
|
||||
#
|
||||
# handler = proc do |req, res|
|
||||
# if res['content-type'] == 'text/plain' then
|
||||
# res.body << "\nThis content was proxied!\n"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# proxy =
|
||||
# WEBrick::HTTPProxyServer.new Port: 8000, ProxyContentHandler: handler
|
||||
|
||||
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 response 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
|
||||
|
||||
# :stopdoc:
|
||||
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
|
||||
public_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("m0")
|
||||
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 credentials")
|
||||
os << "Proxy-Authorization: " << credentials << CRLF
|
||||
end
|
||||
os << CRLF
|
||||
proxy_status_line = os.gets(LF)
|
||||
@logger.debug("CONNECT: read Status-Line from 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 response twice.
|
||||
# see: HTTPServer#run
|
||||
req.parse(NullReader) rescue nil
|
||||
end
|
||||
|
||||
begin
|
||||
while fds = IO::select([ua, os])
|
||||
if fds[0].member?(ua)
|
||||
buf = ua.readpartial(1024);
|
||||
@logger.debug("CONNECT: #{buf.bytesize} byte from User-Agent")
|
||||
os.write(buf)
|
||||
elsif fds[0].member?(os)
|
||||
buf = os.readpartial(1024);
|
||||
@logger.debug("CONNECT: #{buf.bytesize} byte from #{host}:#{port}")
|
||||
ua.write(buf)
|
||||
end
|
||||
end
|
||||
rescue
|
||||
os.close
|
||||
@logger.debug("CONNECT #{host}:#{port}: closed")
|
||||
end
|
||||
|
||||
raise HTTPStatus::EOFError
|
||||
end
|
||||
|
||||
def do_GET(req, res)
|
||||
perform_proxy_request(req, res, Net::HTTP::Get)
|
||||
end
|
||||
|
||||
def do_HEAD(req, res)
|
||||
perform_proxy_request(req, res, Net::HTTP::Head)
|
||||
end
|
||||
|
||||
def do_POST(req, res)
|
||||
perform_proxy_request(req, res, Net::HTTP::Post, req.body_reader)
|
||||
end
|
||||
|
||||
def do_OPTIONS(req, res)
|
||||
res['allow'] = "GET,HEAD,POST,OPTIONS,CONNECT"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Some header fields should not be transferred.
|
||||
HopByHop = %w( connection keep-alive proxy-authenticate upgrade
|
||||
proxy-authorization te trailers transfer-encoding )
|
||||
ShouldNotTransfer = %w( set-cookie proxy-connection )
|
||||
def split_field(f) f ? f.split(/,\s+/).collect{|i| i.downcase } : [] end
|
||||
|
||||
def choose_header(src, dst)
|
||||
connections = split_field(src['connection'])
|
||||
src.each{|key, value|
|
||||
key = key.downcase
|
||||
if HopByHop.member?(key) || # RFC2616: 13.5.1
|
||||
connections.member?(key) || # RFC2616: 14.10
|
||||
ShouldNotTransfer.member?(key) # pragmatics
|
||||
@logger.debug("choose_header: `#{key}: #{value}'")
|
||||
next
|
||||
end
|
||||
dst[key] = value
|
||||
}
|
||||
end
|
||||
|
||||
# Net::HTTP is stupid about the multiple header fields.
|
||||
# Here is workaround:
|
||||
def set_cookie(src, dst)
|
||||
if str = src['set-cookie']
|
||||
cookies = []
|
||||
str.split(/,\s*/).each{|token|
|
||||
if /^[^=]+;/o =~ token
|
||||
cookies[-1] << ", " << token
|
||||
elsif /=/o =~ token
|
||||
cookies << token
|
||||
else
|
||||
cookies[-1] << ", " << token
|
||||
end
|
||||
}
|
||||
dst.cookies.replace(cookies)
|
||||
end
|
||||
end
|
||||
|
||||
def set_via(h)
|
||||
if @config[:ProxyVia]
|
||||
if h['via']
|
||||
h['via'] << ", " << @via
|
||||
else
|
||||
h['via'] = @via
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def setup_proxy_header(req, res)
|
||||
# Choose header fields to transfer
|
||||
header = Hash.new
|
||||
choose_header(req, header)
|
||||
set_via(header)
|
||||
return header
|
||||
end
|
||||
|
||||
def setup_upstream_proxy_authentication(req, res, header)
|
||||
if upstream = proxy_uri(req, res)
|
||||
if upstream.userinfo
|
||||
header['proxy-authorization'] =
|
||||
"Basic " + [upstream.userinfo].pack("m0")
|
||||
end
|
||||
return upstream
|
||||
end
|
||||
return FakeProxyURI
|
||||
end
|
||||
|
||||
def create_net_http(uri, upstream)
|
||||
Net::HTTP.new(uri.host, uri.port, upstream.host, upstream.port)
|
||||
end
|
||||
|
||||
def perform_proxy_request(req, res, req_class, body_stream = nil)
|
||||
uri = req.request_uri
|
||||
path = uri.path.dup
|
||||
path << "?" << uri.query if uri.query
|
||||
header = setup_proxy_header(req, res)
|
||||
upstream = setup_upstream_proxy_authentication(req, res, header)
|
||||
|
||||
body_tmp = []
|
||||
http = create_net_http(uri, upstream)
|
||||
req_fib = Fiber.new do
|
||||
http.start do
|
||||
if @config[:ProxyTimeout]
|
||||
################################## these issues are
|
||||
http.open_timeout = 30 # secs # necessary (maybe because
|
||||
http.read_timeout = 60 # secs # Ruby's bug, but why?)
|
||||
##################################
|
||||
end
|
||||
if body_stream && req['transfer-encoding'] =~ /\bchunked\b/i
|
||||
header['Transfer-Encoding'] = 'chunked'
|
||||
end
|
||||
http_req = req_class.new(path, header)
|
||||
http_req.body_stream = body_stream if body_stream
|
||||
http.request(http_req) do |response|
|
||||
# Persistent connection requirements are mysterious for me.
|
||||
# So I will close the connection in every response.
|
||||
res['proxy-connection'] = "close"
|
||||
res['connection'] = "close"
|
||||
|
||||
# stream Net::HTTP::HTTPResponse to WEBrick::HTTPResponse
|
||||
res.status = response.code.to_i
|
||||
res.chunked = response.chunked?
|
||||
choose_header(response, res)
|
||||
set_cookie(response, res)
|
||||
set_via(res)
|
||||
response.read_body do |buf|
|
||||
body_tmp << buf
|
||||
Fiber.yield # wait for res.body Proc#call
|
||||
end
|
||||
end # http.request
|
||||
end
|
||||
end
|
||||
req_fib.resume # read HTTP response headers and first chunk of the body
|
||||
res.body = ->(socket) do
|
||||
while buf = body_tmp.shift
|
||||
socket.write(buf)
|
||||
buf.clear
|
||||
req_fib.resume # continue response.read_body
|
||||
end
|
||||
end
|
||||
end
|
||||
# :stopdoc:
|
||||
end
|
||||
end
|
|
@ -1,636 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# httprequest.rb -- HTTPRequest Class
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: httprequest.rb,v 1.64 2003/07/13 17:18:22 gotoyuzo Exp $
|
||||
|
||||
require 'fiber'
|
||||
require 'uri'
|
||||
require_relative 'httpversion'
|
||||
require_relative 'httpstatus'
|
||||
require_relative 'httputils'
|
||||
require_relative 'cookie'
|
||||
|
||||
module WEBrick
|
||||
|
||||
##
|
||||
# An HTTP request. This is consumed by service and do_* methods in
|
||||
# WEBrick servlets
|
||||
|
||||
class HTTPRequest
|
||||
|
||||
BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ] # :nodoc:
|
||||
|
||||
# :section: Request line
|
||||
|
||||
##
|
||||
# The complete request line such as:
|
||||
#
|
||||
# GET / HTTP/1.1
|
||||
|
||||
attr_reader :request_line
|
||||
|
||||
##
|
||||
# The request method, GET, POST, PUT, etc.
|
||||
|
||||
attr_reader :request_method
|
||||
|
||||
##
|
||||
# The unparsed URI of the request
|
||||
|
||||
attr_reader :unparsed_uri
|
||||
|
||||
##
|
||||
# The HTTP version of the request
|
||||
|
||||
attr_reader :http_version
|
||||
|
||||
# :section: Request-URI
|
||||
|
||||
##
|
||||
# The parsed URI of the request
|
||||
|
||||
attr_reader :request_uri
|
||||
|
||||
##
|
||||
# The request path
|
||||
|
||||
attr_reader :path
|
||||
|
||||
##
|
||||
# The script name (CGI variable)
|
||||
|
||||
attr_accessor :script_name
|
||||
|
||||
##
|
||||
# The path info (CGI variable)
|
||||
|
||||
attr_accessor :path_info
|
||||
|
||||
##
|
||||
# The query from the URI of the request
|
||||
|
||||
attr_accessor :query_string
|
||||
|
||||
# :section: Header and entity body
|
||||
|
||||
##
|
||||
# The raw header of the request
|
||||
|
||||
attr_reader :raw_header
|
||||
|
||||
##
|
||||
# The parsed header of the request
|
||||
|
||||
attr_reader :header
|
||||
|
||||
##
|
||||
# The parsed request cookies
|
||||
|
||||
attr_reader :cookies
|
||||
|
||||
##
|
||||
# The Accept header value
|
||||
|
||||
attr_reader :accept
|
||||
|
||||
##
|
||||
# The Accept-Charset header value
|
||||
|
||||
attr_reader :accept_charset
|
||||
|
||||
##
|
||||
# The Accept-Encoding header value
|
||||
|
||||
attr_reader :accept_encoding
|
||||
|
||||
##
|
||||
# The Accept-Language header value
|
||||
|
||||
attr_reader :accept_language
|
||||
|
||||
# :section:
|
||||
|
||||
##
|
||||
# The remote user (CGI variable)
|
||||
|
||||
attr_accessor :user
|
||||
|
||||
##
|
||||
# The socket address of the server
|
||||
|
||||
attr_reader :addr
|
||||
|
||||
##
|
||||
# The socket address of the client
|
||||
|
||||
attr_reader :peeraddr
|
||||
|
||||
##
|
||||
# Hash of request attributes
|
||||
|
||||
attr_reader :attributes
|
||||
|
||||
##
|
||||
# Is this a keep-alive connection?
|
||||
|
||||
attr_reader :keep_alive
|
||||
|
||||
##
|
||||
# The local time this request was received
|
||||
|
||||
attr_reader :request_time
|
||||
|
||||
##
|
||||
# Creates a new HTTP request. WEBrick::Config::HTTP is the default
|
||||
# configuration.
|
||||
|
||||
def initialize(config)
|
||||
@config = config
|
||||
@buffer_size = @config[:InputBufferSize]
|
||||
@logger = config[:Logger]
|
||||
|
||||
@request_line = @request_method =
|
||||
@unparsed_uri = @http_version = nil
|
||||
|
||||
@request_uri = @host = @port = @path = nil
|
||||
@script_name = @path_info = nil
|
||||
@query_string = nil
|
||||
@query = nil
|
||||
@form_data = nil
|
||||
|
||||
@raw_header = Array.new
|
||||
@header = nil
|
||||
@cookies = []
|
||||
@accept = []
|
||||
@accept_charset = []
|
||||
@accept_encoding = []
|
||||
@accept_language = []
|
||||
@body = ""
|
||||
|
||||
@addr = @peeraddr = nil
|
||||
@attributes = {}
|
||||
@user = nil
|
||||
@keep_alive = false
|
||||
@request_time = nil
|
||||
|
||||
@remaining_size = nil
|
||||
@socket = nil
|
||||
|
||||
@forwarded_proto = @forwarded_host = @forwarded_port =
|
||||
@forwarded_server = @forwarded_for = nil
|
||||
end
|
||||
|
||||
##
|
||||
# Parses a request from +socket+. This is called internally by
|
||||
# WEBrick::HTTPServer.
|
||||
|
||||
def parse(socket=nil)
|
||||
@socket = socket
|
||||
begin
|
||||
@peeraddr = socket.respond_to?(:peeraddr) ? socket.peeraddr : []
|
||||
@addr = socket.respond_to?(:addr) ? socket.addr : []
|
||||
rescue Errno::ENOTCONN
|
||||
raise HTTPStatus::EOFError
|
||||
end
|
||||
|
||||
read_request_line(socket)
|
||||
if @http_version.major > 0
|
||||
read_header(socket)
|
||||
@header['cookie'].each{|cookie|
|
||||
@cookies += Cookie::parse(cookie)
|
||||
}
|
||||
@accept = HTTPUtils.parse_qvalues(self['accept'])
|
||||
@accept_charset = HTTPUtils.parse_qvalues(self['accept-charset'])
|
||||
@accept_encoding = HTTPUtils.parse_qvalues(self['accept-encoding'])
|
||||
@accept_language = HTTPUtils.parse_qvalues(self['accept-language'])
|
||||
end
|
||||
return if @request_method == "CONNECT"
|
||||
return if @unparsed_uri == "*"
|
||||
|
||||
begin
|
||||
setup_forwarded_info
|
||||
@request_uri = parse_uri(@unparsed_uri)
|
||||
@path = HTTPUtils::unescape(@request_uri.path)
|
||||
@path = HTTPUtils::normalize_path(@path)
|
||||
@host = @request_uri.host
|
||||
@port = @request_uri.port
|
||||
@query_string = @request_uri.query
|
||||
@script_name = ""
|
||||
@path_info = @path.dup
|
||||
rescue
|
||||
raise HTTPStatus::BadRequest, "bad URI `#{@unparsed_uri}'."
|
||||
end
|
||||
|
||||
if /\Aclose\z/io =~ self["connection"]
|
||||
@keep_alive = false
|
||||
elsif /\Akeep-alive\z/io =~ self["connection"]
|
||||
@keep_alive = true
|
||||
elsif @http_version < "1.1"
|
||||
@keep_alive = false
|
||||
else
|
||||
@keep_alive = true
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Generate HTTP/1.1 100 continue response if the client expects it,
|
||||
# otherwise does nothing.
|
||||
|
||||
def continue # :nodoc:
|
||||
if self['expect'] == '100-continue' && @config[:HTTPVersion] >= "1.1"
|
||||
@socket << "HTTP/#{@config[:HTTPVersion]} 100 continue#{CRLF}#{CRLF}"
|
||||
@header.delete('expect')
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the request body.
|
||||
|
||||
def body(&block) # :yields: body_chunk
|
||||
block ||= Proc.new{|chunk| @body << chunk }
|
||||
read_body(@socket, block)
|
||||
@body.empty? ? nil : @body
|
||||
end
|
||||
|
||||
##
|
||||
# Prepares the HTTPRequest object for use as the
|
||||
# source for IO.copy_stream
|
||||
|
||||
def body_reader
|
||||
@body_tmp = []
|
||||
@body_rd = Fiber.new do
|
||||
body do |buf|
|
||||
@body_tmp << buf
|
||||
Fiber.yield
|
||||
end
|
||||
end
|
||||
@body_rd.resume # grab the first chunk and yield
|
||||
self
|
||||
end
|
||||
|
||||
# for IO.copy_stream.
|
||||
def readpartial(size, buf = ''.b) # :nodoc
|
||||
res = @body_tmp.shift or raise EOFError, 'end of file reached'
|
||||
if res.length > size
|
||||
@body_tmp.unshift(res[size..-1])
|
||||
res = res[0..size - 1]
|
||||
end
|
||||
buf.replace(res)
|
||||
res.clear
|
||||
# get more chunks - check alive? because we can take a partial chunk
|
||||
@body_rd.resume if @body_rd.alive?
|
||||
buf
|
||||
end
|
||||
|
||||
##
|
||||
# Request query as a Hash
|
||||
|
||||
def query
|
||||
unless @query
|
||||
parse_query()
|
||||
end
|
||||
@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]
|
||||
value.empty? ? nil : value.join(", ")
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Iterates over the request headers
|
||||
|
||||
def each
|
||||
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 # :nodoc:
|
||||
ret = @request_line.dup
|
||||
@raw_header.each{|line| ret << line }
|
||||
ret << CRLF
|
||||
ret << body if body
|
||||
ret
|
||||
end
|
||||
|
||||
##
|
||||
# Consumes any remaining body and updates keep-alive status
|
||||
|
||||
def fixup() # :nodoc:
|
||||
begin
|
||||
body{|chunk| } # read remaining body
|
||||
rescue HTTPStatus::Error => ex
|
||||
@logger.error("HTTPRequest#fixup: #{ex.class} occurred.")
|
||||
@keep_alive = false
|
||||
rescue => ex
|
||||
@logger.error(ex)
|
||||
@keep_alive = false
|
||||
end
|
||||
end
|
||||
|
||||
# This method provides the metavariables defined by the revision 3
|
||||
# of "The WWW Common Gateway Interface Version 1.1"
|
||||
# To browse the current document of CGI Version 1.1, see below:
|
||||
# https://www.rfc-editor.org/rfc/rfc3875
|
||||
|
||||
def meta_vars
|
||||
meta = Hash.new
|
||||
|
||||
cl = self["Content-Length"]
|
||||
ct = self["Content-Type"]
|
||||
meta["CONTENT_LENGTH"] = cl if cl.to_i > 0
|
||||
meta["CONTENT_TYPE"] = ct.dup if ct
|
||||
meta["GATEWAY_INTERFACE"] = "CGI/1.1"
|
||||
meta["PATH_INFO"] = @path_info ? @path_info.dup : ""
|
||||
#meta["PATH_TRANSLATED"] = nil # no plan to be provided
|
||||
meta["QUERY_STRING"] = @query_string ? @query_string.dup : ""
|
||||
meta["REMOTE_ADDR"] = @peeraddr[3]
|
||||
meta["REMOTE_HOST"] = @peeraddr[2]
|
||||
#meta["REMOTE_IDENT"] = nil # no plan to be provided
|
||||
meta["REMOTE_USER"] = @user
|
||||
meta["REQUEST_METHOD"] = @request_method.dup
|
||||
meta["REQUEST_URI"] = @request_uri.to_s
|
||||
meta["SCRIPT_NAME"] = @script_name.dup
|
||||
meta["SERVER_NAME"] = @host
|
||||
meta["SERVER_PORT"] = @port.to_s
|
||||
meta["SERVER_PROTOCOL"] = "HTTP/" + @config[:HTTPVersion].to_s
|
||||
meta["SERVER_SOFTWARE"] = @config[:ServerSoftware].dup
|
||||
|
||||
self.each{|key, val|
|
||||
next if /^content-type$/i =~ key
|
||||
next if /^content-length$/i =~ key
|
||||
name = "HTTP_" + key
|
||||
name.gsub!(/-/o, "_")
|
||||
name.upcase!
|
||||
meta[name] = val
|
||||
}
|
||||
|
||||
meta
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
MAX_URI_LENGTH = 2083 # :nodoc:
|
||||
|
||||
# same as Mongrel, Thin and Puma
|
||||
MAX_HEADER_LENGTH = (112 * 1024) # :nodoc:
|
||||
|
||||
def read_request_line(socket)
|
||||
@request_line = read_line(socket, MAX_URI_LENGTH) if socket
|
||||
raise HTTPStatus::EOFError unless @request_line
|
||||
|
||||
@request_bytes = @request_line.bytesize
|
||||
if @request_bytes >= MAX_URI_LENGTH and @request_line[-1, 1] != LF
|
||||
raise HTTPStatus::RequestURITooLarge
|
||||
end
|
||||
|
||||
@request_time = Time.now
|
||||
if /^(\S+)\s+(\S++)(?:\s+HTTP\/(\d+\.\d+))?\r?\n/mo =~ @request_line
|
||||
@request_method = $1
|
||||
@unparsed_uri = $2
|
||||
@http_version = HTTPVersion.new($3 ? $3 : "0.9")
|
||||
else
|
||||
rl = @request_line.sub(/\x0d?\x0a\z/o, '')
|
||||
raise HTTPStatus::BadRequest, "bad Request-Line `#{rl}'."
|
||||
end
|
||||
end
|
||||
|
||||
def read_header(socket)
|
||||
if socket
|
||||
while line = read_line(socket)
|
||||
break if /\A(#{CRLF}|#{LF})\z/om =~ line
|
||||
if (@request_bytes += line.bytesize) > MAX_HEADER_LENGTH
|
||||
raise HTTPStatus::RequestEntityTooLarge, 'headers too large'
|
||||
end
|
||||
@raw_header << line
|
||||
end
|
||||
end
|
||||
@header = HTTPUtils::parse_header(@raw_header.join)
|
||||
end
|
||||
|
||||
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
|
||||
host, port = @forwarded_host, @forwarded_port
|
||||
elsif self["host"]
|
||||
pattern = /\A(#{URI::REGEXP::PATTERN::HOST})(?::(\d+))?\z/n
|
||||
host, port = *self['host'].scan(pattern)[0]
|
||||
elsif @addr.size > 0
|
||||
host, port = @addr[2], @addr[1]
|
||||
else
|
||||
host, port = @config[:ServerName], @config[:Port]
|
||||
end
|
||||
uri.scheme = @forwarded_proto || scheme
|
||||
uri.host = host
|
||||
uri.port = port ? port.to_i : nil
|
||||
return URI::parse(uri.to_s)
|
||||
end
|
||||
|
||||
def read_body(socket, block)
|
||||
return unless socket
|
||||
if tc = self['transfer-encoding']
|
||||
case tc
|
||||
when /\Achunked\z/io then read_chunked(socket, block)
|
||||
else raise HTTPStatus::NotImplemented, "Transfer-Encoding: #{tc}."
|
||||
end
|
||||
elsif self['content-length'] || @remaining_size
|
||||
@remaining_size ||= self['content-length'].to_i
|
||||
while @remaining_size > 0
|
||||
sz = [@buffer_size, @remaining_size].min
|
||||
break unless buf = read_data(socket, sz)
|
||||
@remaining_size -= buf.bytesize
|
||||
block.call(buf)
|
||||
end
|
||||
if @remaining_size > 0 && @socket.eof?
|
||||
raise HTTPStatus::BadRequest, "invalid body size."
|
||||
end
|
||||
elsif BODY_CONTAINABLE_METHODS.member?(@request_method) && !@socket.eof
|
||||
raise HTTPStatus::LengthRequired
|
||||
end
|
||||
return @body
|
||||
end
|
||||
|
||||
def read_chunk_size(socket)
|
||||
line = read_line(socket)
|
||||
if /^([0-9a-fA-F]+)(?:;(\S+))?/ =~ line
|
||||
chunk_size = $1.hex
|
||||
chunk_ext = $2
|
||||
[ chunk_size, chunk_ext ]
|
||||
else
|
||||
raise HTTPStatus::BadRequest, "bad chunk `#{line}'."
|
||||
end
|
||||
end
|
||||
|
||||
def read_chunked(socket, block)
|
||||
chunk_size, = read_chunk_size(socket)
|
||||
while chunk_size > 0
|
||||
begin
|
||||
sz = [ chunk_size, @buffer_size ].min
|
||||
data = read_data(socket, sz) # read chunk-data
|
||||
if data.nil? || data.bytesize != sz
|
||||
raise HTTPStatus::BadRequest, "bad chunk data size."
|
||||
end
|
||||
block.call(data)
|
||||
end while (chunk_size -= sz) > 0
|
||||
|
||||
read_line(socket) # skip CRLF
|
||||
chunk_size, = read_chunk_size(socket)
|
||||
end
|
||||
read_header(socket) # trailer + CRLF
|
||||
@header.delete("transfer-encoding")
|
||||
@remaining_size = 0
|
||||
end
|
||||
|
||||
def _read_data(io, method, *arg)
|
||||
begin
|
||||
WEBrick::Utils.timeout(@config[:RequestTimeout]){
|
||||
return io.__send__(method, *arg)
|
||||
}
|
||||
rescue Errno::ECONNRESET
|
||||
return nil
|
||||
rescue Timeout::Error
|
||||
raise HTTPStatus::RequestTimeout
|
||||
end
|
||||
end
|
||||
|
||||
def read_line(io, size=4096)
|
||||
_read_data(io, :gets, LF, size)
|
||||
end
|
||||
|
||||
def read_data(io, size)
|
||||
_read_data(io, :read, size)
|
||||
end
|
||||
|
||||
def parse_query()
|
||||
begin
|
||||
if @request_method == "GET" || @request_method == "HEAD"
|
||||
@query = HTTPUtils::parse_query(@query_string)
|
||||
elsif self['content-type'] =~ /^application\/x-www-form-urlencoded/
|
||||
@query = HTTPUtils::parse_query(body)
|
||||
elsif self['content-type'] =~ /^multipart\/form-data; boundary=(.+)/
|
||||
boundary = HTTPUtils::dequote($1)
|
||||
@query = HTTPUtils::parse_form_data(body, boundary)
|
||||
else
|
||||
@query = Hash.new
|
||||
end
|
||||
rescue => ex
|
||||
raise HTTPStatus::BadRequest, ex.message
|
||||
end
|
||||
end
|
||||
|
||||
PrivateNetworkRegexp = /
|
||||
^unknown$|
|
||||
^((::ffff:)?127.0.0.1|::1)$|
|
||||
^(::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
|
||||
if @forwarded_server = self["x-forwarded-server"]
|
||||
@forwarded_server = @forwarded_server.split(",", 2).first
|
||||
end
|
||||
if @forwarded_proto = self["x-forwarded-proto"]
|
||||
@forwarded_proto = @forwarded_proto.split(",", 2).first
|
||||
end
|
||||
if host_port = self["x-forwarded-host"]
|
||||
host_port = host_port.split(",", 2).first
|
||||
if host_port =~ /\A(\[[0-9a-fA-F:]+\])(?::(\d+))?\z/
|
||||
@forwarded_host = $1
|
||||
tmp = $2
|
||||
else
|
||||
@forwarded_host, tmp = host_port.split(":", 2)
|
||||
end
|
||||
@forwarded_port = (tmp || (@forwarded_proto == "https" ? 443 : 80)).to_i
|
||||
end
|
||||
if addrs = self["x-forwarded-for"]
|
||||
addrs = addrs.split(",").collect(&:strip)
|
||||
addrs.reject!{|ip| PrivateNetworkRegexp =~ ip }
|
||||
@forwarded_for = addrs.first
|
||||
end
|
||||
end
|
||||
|
||||
# :startdoc:
|
||||
end
|
||||
end
|
|
@ -1,564 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# httpresponse.rb -- HTTPResponse Class
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: httpresponse.rb,v 1.45 2003/07/11 11:02:25 gotoyuzo Exp $
|
||||
|
||||
require 'time'
|
||||
require 'uri'
|
||||
require_relative 'httpversion'
|
||||
require_relative 'htmlutils'
|
||||
require_relative 'httputils'
|
||||
require_relative 'httpstatus'
|
||||
|
||||
module WEBrick
|
||||
##
|
||||
# An HTTP response. This is filled in by the service or do_* methods of a
|
||||
# WEBrick HTTP Servlet.
|
||||
|
||||
class HTTPResponse
|
||||
class InvalidHeader < StandardError
|
||||
end
|
||||
|
||||
##
|
||||
# HTTP Response version
|
||||
|
||||
attr_reader :http_version
|
||||
|
||||
##
|
||||
# Response status code (200)
|
||||
|
||||
attr_reader :status
|
||||
|
||||
##
|
||||
# Response header
|
||||
|
||||
attr_reader :header
|
||||
|
||||
##
|
||||
# Response cookies
|
||||
|
||||
attr_reader :cookies
|
||||
|
||||
##
|
||||
# Response reason phrase ("OK")
|
||||
|
||||
attr_accessor :reason_phrase
|
||||
|
||||
##
|
||||
# Body may be:
|
||||
# * a String;
|
||||
# * an IO-like object that responds to +#read+ and +#readpartial+;
|
||||
# * a Proc-like object that responds to +#call+.
|
||||
#
|
||||
# In the latter case, either #chunked= should be set to +true+,
|
||||
# or <code>header['content-length']</code> explicitly provided.
|
||||
# Example:
|
||||
#
|
||||
# server.mount_proc '/' do |req, res|
|
||||
# res.chunked = true
|
||||
# # or
|
||||
# # res.header['content-length'] = 10
|
||||
# res.body = proc { |out| out.write(Time.now.to_s) }
|
||||
# end
|
||||
|
||||
attr_accessor :body
|
||||
|
||||
##
|
||||
# Request method for this response
|
||||
|
||||
attr_accessor :request_method
|
||||
|
||||
##
|
||||
# Request URI for this response
|
||||
|
||||
attr_accessor :request_uri
|
||||
|
||||
##
|
||||
# Request HTTP version for this response
|
||||
|
||||
attr_accessor :request_http_version
|
||||
|
||||
##
|
||||
# Filename of the static file in this response. Only used by the
|
||||
# FileHandler servlet.
|
||||
|
||||
attr_accessor :filename
|
||||
|
||||
##
|
||||
# Is this a keep-alive response?
|
||||
|
||||
attr_accessor :keep_alive
|
||||
|
||||
##
|
||||
# Configuration for this response
|
||||
|
||||
attr_reader :config
|
||||
|
||||
##
|
||||
# Bytes sent in this response
|
||||
|
||||
attr_reader :sent_size
|
||||
|
||||
##
|
||||
# Creates a new HTTP response object. WEBrick::Config::HTTP is the
|
||||
# default configuration.
|
||||
|
||||
def initialize(config)
|
||||
@config = config
|
||||
@buffer_size = config[:OutputBufferSize]
|
||||
@logger = config[:Logger]
|
||||
@header = Hash.new
|
||||
@status = HTTPStatus::RC_OK
|
||||
@reason_phrase = nil
|
||||
@http_version = HTTPVersion::convert(@config[:HTTPVersion])
|
||||
@body = ''
|
||||
@keep_alive = true
|
||||
@cookies = []
|
||||
@request_method = nil
|
||||
@request_uri = nil
|
||||
@request_http_version = @http_version # temporary
|
||||
@chunked = false
|
||||
@filename = nil
|
||||
@sent_size = 0
|
||||
@bodytempfile = nil
|
||||
end
|
||||
|
||||
##
|
||||
# The response's HTTP status line
|
||||
|
||||
def status_line
|
||||
"HTTP/#@http_version #@status #@reason_phrase".rstrip << 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)
|
||||
@chunked = value.to_s.downcase == 'chunked' if field.downcase == 'transfer-encoding'
|
||||
@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 response
|
||||
|
||||
def each
|
||||
@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) # :nodoc:
|
||||
begin
|
||||
setup_header()
|
||||
send_header(socket)
|
||||
send_body(socket)
|
||||
rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN => ex
|
||||
@logger.debug(ex)
|
||||
@keep_alive = false
|
||||
rescue Exception => ex
|
||||
@logger.error(ex)
|
||||
@keep_alive = false
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Sets up the headers for sending
|
||||
|
||||
def setup_header() # :nodoc:
|
||||
@reason_phrase ||= HTTPStatus::reason_phrase(@status)
|
||||
@header['server'] ||= @config[:ServerSoftware]
|
||||
@header['date'] ||= Time.now.httpdate
|
||||
|
||||
# HTTP/0.9 features
|
||||
if @request_http_version < "1.0"
|
||||
@http_version = HTTPVersion.new("0.9")
|
||||
@keep_alive = false
|
||||
end
|
||||
|
||||
# HTTP/1.0 features
|
||||
if @request_http_version < "1.1"
|
||||
if chunked?
|
||||
@chunked = false
|
||||
ver = @request_http_version.to_s
|
||||
msg = "chunked is set for an HTTP/#{ver} request. (ignored)"
|
||||
@logger.warn(msg)
|
||||
end
|
||||
end
|
||||
|
||||
# Determine the message length (RFC2616 -- 4.4 Message Length)
|
||||
if @status == 304 || @status == 204 || HTTPStatus::info?(@status)
|
||||
@header.delete('content-length')
|
||||
@body = ""
|
||||
elsif chunked?
|
||||
@header["transfer-encoding"] = "chunked"
|
||||
@header.delete('content-length')
|
||||
elsif %r{^multipart/byteranges} =~ @header['content-type']
|
||||
@header.delete('content-length')
|
||||
elsif @header['content-length'].nil?
|
||||
if @body.respond_to? :readpartial
|
||||
elsif @body.respond_to? :call
|
||||
make_body_tempfile
|
||||
else
|
||||
@header['content-length'] = (@body ? @body.bytesize : 0).to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Keep-Alive connection.
|
||||
if @header['connection'] == "close"
|
||||
@keep_alive = false
|
||||
elsif keep_alive?
|
||||
if chunked? || @header['content-length'] || @status == 304 || @status == 204 || HTTPStatus.info?(@status)
|
||||
@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"
|
||||
end
|
||||
|
||||
# Location is a single absoluteURI.
|
||||
if location = @header['location']
|
||||
if @request_uri
|
||||
@header['location'] = @request_uri.merge(location).to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_body_tempfile # :nodoc:
|
||||
return if @bodytempfile
|
||||
bodytempfile = Tempfile.create("webrick")
|
||||
if @body.nil?
|
||||
# nothing
|
||||
elsif @body.respond_to? :readpartial
|
||||
IO.copy_stream(@body, bodytempfile)
|
||||
@body.close
|
||||
elsif @body.respond_to? :call
|
||||
@body.call(bodytempfile)
|
||||
else
|
||||
bodytempfile.write @body
|
||||
end
|
||||
bodytempfile.rewind
|
||||
@body = @bodytempfile = bodytempfile
|
||||
@header['content-length'] = bodytempfile.stat.size.to_s
|
||||
end
|
||||
|
||||
def remove_body_tempfile # :nodoc:
|
||||
if @bodytempfile
|
||||
@bodytempfile.close
|
||||
File.unlink @bodytempfile.path
|
||||
@bodytempfile = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
##
|
||||
# Sends the headers on +socket+
|
||||
|
||||
def send_header(socket) # :nodoc:
|
||||
if @http_version.major > 0
|
||||
data = status_line()
|
||||
@header.each{|key, value|
|
||||
tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }
|
||||
data << "#{tmp}: #{check_header(value)}" << CRLF
|
||||
}
|
||||
@cookies.each{|cookie|
|
||||
data << "Set-Cookie: " << check_header(cookie.to_s) << CRLF
|
||||
}
|
||||
data << CRLF
|
||||
socket.write(data)
|
||||
end
|
||||
rescue InvalidHeader => e
|
||||
@header.clear
|
||||
@cookies.clear
|
||||
set_error e
|
||||
retry
|
||||
end
|
||||
|
||||
##
|
||||
# Sends the body on +socket+
|
||||
|
||||
def send_body(socket) # :nodoc:
|
||||
if @body.respond_to? :readpartial then
|
||||
send_body_io(socket)
|
||||
elsif @body.respond_to?(:call) then
|
||||
send_body_proc(socket)
|
||||
else
|
||||
send_body_string(socket)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Redirects to +url+ with a WEBrick::HTTPStatus::Redirect +status+.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# res.set_redirect WEBrick::HTTPStatus::TemporaryRedirect
|
||||
|
||||
def set_redirect(status, url)
|
||||
url = URI(url).to_s
|
||||
@body = "<HTML><A HREF=\"#{url}\">#{url}</A>.</HTML>\n"
|
||||
@header['location'] = url
|
||||
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
|
||||
@keep_alive = false if HTTPStatus::error?(ex.code)
|
||||
self.status = ex.code
|
||||
else
|
||||
@keep_alive = false
|
||||
self.status = HTTPStatus::RC_INTERNAL_SERVER_ERROR
|
||||
end
|
||||
@header['content-type'] = "text/html; charset=ISO-8859-1"
|
||||
|
||||
if respond_to?(:create_error_page)
|
||||
create_error_page()
|
||||
return
|
||||
end
|
||||
|
||||
if @request_uri
|
||||
host, port = @request_uri.host, @request_uri.port
|
||||
else
|
||||
host, port = @config[:ServerName], @config[:Port]
|
||||
end
|
||||
|
||||
error_body(backtrace, ex, host, port)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_header(header_value)
|
||||
header_value = header_value.to_s
|
||||
if /[\r\n]/ =~ header_value
|
||||
raise InvalidHeader
|
||||
else
|
||||
header_value
|
||||
end
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
def error_body(backtrace, ex, host, port)
|
||||
@body = ''
|
||||
@body << <<-_end_of_html_
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
|
||||
<HTML>
|
||||
<HEAD><TITLE>#{HTMLUtils::escape(@reason_phrase)}</TITLE></HEAD>
|
||||
<BODY>
|
||||
<H1>#{HTMLUtils::escape(@reason_phrase)}</H1>
|
||||
#{HTMLUtils::escape(ex.message)}
|
||||
<HR>
|
||||
_end_of_html_
|
||||
|
||||
if backtrace && $DEBUG
|
||||
@body << "backtrace of `#{HTMLUtils::escape(ex.class.to_s)}' "
|
||||
@body << "#{HTMLUtils::escape(ex.message)}"
|
||||
@body << "<PRE>"
|
||||
ex.backtrace.each{|line| @body << "\t#{line}\n"}
|
||||
@body << "</PRE><HR>"
|
||||
end
|
||||
|
||||
@body << <<-_end_of_html_
|
||||
<ADDRESS>
|
||||
#{HTMLUtils::escape(@config[:ServerSoftware])} at
|
||||
#{host}:#{port}
|
||||
</ADDRESS>
|
||||
</BODY>
|
||||
</HTML>
|
||||
_end_of_html_
|
||||
end
|
||||
|
||||
def send_body_io(socket)
|
||||
begin
|
||||
if @request_method == "HEAD"
|
||||
# do nothing
|
||||
elsif chunked?
|
||||
buf = ''
|
||||
begin
|
||||
@body.readpartial(@buffer_size, buf)
|
||||
size = buf.bytesize
|
||||
data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
|
||||
socket.write(data)
|
||||
data.clear
|
||||
@sent_size += size
|
||||
rescue EOFError
|
||||
break
|
||||
end while true
|
||||
buf.clear
|
||||
socket.write("0#{CRLF}#{CRLF}")
|
||||
else
|
||||
if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ @header['content-range']
|
||||
offset = $1.to_i
|
||||
size = $2.to_i - offset + 1
|
||||
else
|
||||
offset = nil
|
||||
size = @header['content-length']
|
||||
size = size.to_i if size
|
||||
end
|
||||
begin
|
||||
@sent_size = IO.copy_stream(@body, socket, size, offset)
|
||||
rescue NotImplementedError
|
||||
@body.seek(offset, IO::SEEK_SET)
|
||||
@sent_size = IO.copy_stream(@body, socket, size)
|
||||
end
|
||||
end
|
||||
ensure
|
||||
@body.close
|
||||
end
|
||||
remove_body_tempfile
|
||||
end
|
||||
|
||||
def send_body_string(socket)
|
||||
if @request_method == "HEAD"
|
||||
# do nothing
|
||||
elsif chunked?
|
||||
body ? @body.bytesize : 0
|
||||
while buf = @body[@sent_size, @buffer_size]
|
||||
break if buf.empty?
|
||||
size = buf.bytesize
|
||||
data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
|
||||
buf.clear
|
||||
socket.write(data)
|
||||
@sent_size += size
|
||||
end
|
||||
socket.write("0#{CRLF}#{CRLF}")
|
||||
else
|
||||
if @body && @body.bytesize > 0
|
||||
socket.write(@body)
|
||||
@sent_size = @body.bytesize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def send_body_proc(socket)
|
||||
if @request_method == "HEAD"
|
||||
# do nothing
|
||||
elsif chunked?
|
||||
@body.call(ChunkedWrapper.new(socket, self))
|
||||
socket.write("0#{CRLF}#{CRLF}")
|
||||
else
|
||||
size = @header['content-length'].to_i
|
||||
if @bodytempfile
|
||||
@bodytempfile.rewind
|
||||
IO.copy_stream(@bodytempfile, socket)
|
||||
else
|
||||
@body.call(socket)
|
||||
end
|
||||
@sent_size = size
|
||||
end
|
||||
end
|
||||
|
||||
class ChunkedWrapper
|
||||
def initialize(socket, resp)
|
||||
@socket = socket
|
||||
@resp = resp
|
||||
end
|
||||
|
||||
def write(buf)
|
||||
return 0 if buf.empty?
|
||||
socket = @socket
|
||||
@resp.instance_eval {
|
||||
size = buf.bytesize
|
||||
data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
|
||||
socket.write(data)
|
||||
data.clear
|
||||
@sent_size += size
|
||||
size
|
||||
}
|
||||
end
|
||||
|
||||
def <<(*buf)
|
||||
write(buf)
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
# preserved for compatibility with some 3rd-party handlers
|
||||
def _write_data(socket, data)
|
||||
socket << data
|
||||
end
|
||||
|
||||
# :startdoc:
|
||||
end
|
||||
|
||||
end
|
|
@ -1,152 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# https.rb -- SSL/TLS enhancement for HTTPServer
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2001 GOTOU Yuuzou
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: https.rb,v 1.15 2003/07/22 19:20:42 gotoyuzo Exp $
|
||||
|
||||
require_relative 'ssl'
|
||||
require_relative 'httpserver'
|
||||
|
||||
module WEBrick
|
||||
module Config
|
||||
HTTP.update(SSL)
|
||||
end
|
||||
|
||||
##
|
||||
#--
|
||||
# Adds SSL functionality to WEBrick::HTTPRequest
|
||||
|
||||
class HTTPRequest
|
||||
|
||||
##
|
||||
# HTTP request SSL cipher
|
||||
|
||||
attr_reader :cipher
|
||||
|
||||
##
|
||||
# HTTP request server certificate
|
||||
|
||||
attr_reader :server_cert
|
||||
|
||||
##
|
||||
# HTTP request client certificate
|
||||
|
||||
attr_reader :client_cert
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
alias orig_parse parse
|
||||
|
||||
def parse(socket=nil)
|
||||
if socket.respond_to?(:cert)
|
||||
@server_cert = socket.cert || @config[:SSLCertificate]
|
||||
@client_cert = socket.peer_cert
|
||||
@client_cert_chain = socket.peer_cert_chain
|
||||
@cipher = socket.cipher
|
||||
end
|
||||
orig_parse(socket)
|
||||
end
|
||||
|
||||
alias orig_parse_uri parse_uri
|
||||
|
||||
def parse_uri(str, scheme="https")
|
||||
if server_cert
|
||||
return orig_parse_uri(str, scheme)
|
||||
end
|
||||
return orig_parse_uri(str)
|
||||
end
|
||||
private :parse_uri
|
||||
|
||||
alias orig_meta_vars meta_vars
|
||||
|
||||
def meta_vars
|
||||
meta = orig_meta_vars
|
||||
if server_cert
|
||||
meta["HTTPS"] = "on"
|
||||
meta["SSL_SERVER_CERT"] = @server_cert.to_pem
|
||||
meta["SSL_CLIENT_CERT"] = @client_cert ? @client_cert.to_pem : ""
|
||||
if @client_cert_chain
|
||||
@client_cert_chain.each_with_index{|cert, i|
|
||||
meta["SSL_CLIENT_CERT_CHAIN_#{i}"] = cert.to_pem
|
||||
}
|
||||
end
|
||||
meta["SSL_CIPHER"] = @cipher[0]
|
||||
meta["SSL_PROTOCOL"] = @cipher[1]
|
||||
meta["SSL_CIPHER_USEKEYSIZE"] = @cipher[2].to_s
|
||||
meta["SSL_CIPHER_ALGKEYSIZE"] = @cipher[3].to_s
|
||||
end
|
||||
meta
|
||||
end
|
||||
|
||||
# :startdoc:
|
||||
end
|
||||
|
||||
##
|
||||
#--
|
||||
# Fake WEBrick::HTTPRequest for lookup_server
|
||||
|
||||
class SNIRequest
|
||||
|
||||
##
|
||||
# The SNI hostname
|
||||
|
||||
attr_reader :host
|
||||
|
||||
##
|
||||
# The socket address of the server
|
||||
|
||||
attr_reader :addr
|
||||
|
||||
##
|
||||
# The port this request is for
|
||||
|
||||
attr_reader :port
|
||||
|
||||
##
|
||||
# Creates a new SNIRequest.
|
||||
|
||||
def initialize(sslsocket, hostname)
|
||||
@host = hostname
|
||||
@addr = sslsocket.addr
|
||||
@port = @addr[1]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
##
|
||||
#--
|
||||
# Adds SSL functionality to WEBrick::HTTPServer
|
||||
|
||||
class HTTPServer < ::WEBrick::GenericServer
|
||||
##
|
||||
# ServerNameIndication callback
|
||||
|
||||
def ssl_servername_callback(sslsocket, hostname = nil)
|
||||
req = SNIRequest.new(sslsocket, hostname)
|
||||
server = lookup_server(req)
|
||||
server ? server.ssl_context : nil
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
##
|
||||
# Check whether +server+ is also SSL server.
|
||||
# Also +server+'s SSL context will be created.
|
||||
|
||||
alias orig_virtual_host virtual_host
|
||||
|
||||
def virtual_host(server)
|
||||
if @config[:SSLEnable] && !server.ssl_context
|
||||
raise ArgumentError, "virtual host must set SSLEnable to true"
|
||||
end
|
||||
orig_virtual_host(server)
|
||||
end
|
||||
|
||||
# :startdoc:
|
||||
end
|
||||
end
|
|
@ -1,293 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# httpserver.rb -- HTTPServer Class
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: httpserver.rb,v 1.63 2002/10/01 17:16:32 gotoyuzo Exp $
|
||||
|
||||
require_relative 'server'
|
||||
require_relative 'httputils'
|
||||
require_relative 'httpstatus'
|
||||
require_relative 'httprequest'
|
||||
require_relative 'httpresponse'
|
||||
require_relative 'httpservlet'
|
||||
require_relative 'accesslog'
|
||||
|
||||
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])
|
||||
|
||||
@mount_tab = MountTable.new
|
||||
if @config[:DocumentRoot]
|
||||
mount("/", HTTPServlet::FileHandler, @config[:DocumentRoot],
|
||||
@config[:DocumentRootOptions])
|
||||
end
|
||||
|
||||
unless @config[:AccessLog]
|
||||
@config[:AccessLog] = [
|
||||
[ $stderr, AccessLog::COMMON_LOG_FORMAT ],
|
||||
[ $stderr, AccessLog::REFERER_LOG_FORMAT ]
|
||||
]
|
||||
end
|
||||
|
||||
@virtual_hosts = Array.new
|
||||
end
|
||||
|
||||
##
|
||||
# Processes requests on +sock+
|
||||
|
||||
def run(sock)
|
||||
while true
|
||||
req = create_request(@config)
|
||||
res = create_response(@config)
|
||||
server = self
|
||||
begin
|
||||
timeout = @config[:RequestTimeout]
|
||||
while timeout > 0
|
||||
break if sock.to_io.wait_readable(0.5)
|
||||
break if @status != :Running
|
||||
timeout -= 0.5
|
||||
end
|
||||
raise HTTPStatus::EOFError if timeout <= 0 || @status != :Running
|
||||
raise HTTPStatus::EOFError if sock.eof?
|
||||
req.parse(sock)
|
||||
res.request_method = req.request_method
|
||||
res.request_uri = req.request_uri
|
||||
res.request_http_version = req.http_version
|
||||
res.keep_alive = req.keep_alive?
|
||||
server = lookup_server(req) || self
|
||||
if callback = server[:RequestCallback]
|
||||
callback.call(req, res)
|
||||
elsif callback = server[:RequestHandler]
|
||||
msg = ":RequestHandler is deprecated, please use :RequestCallback"
|
||||
@logger.warn(msg)
|
||||
callback.call(req, res)
|
||||
end
|
||||
server.service(req, res)
|
||||
rescue HTTPStatus::EOFError, HTTPStatus::RequestTimeout => ex
|
||||
res.set_error(ex)
|
||||
rescue HTTPStatus::Error => ex
|
||||
@logger.error(ex.message)
|
||||
res.set_error(ex)
|
||||
rescue HTTPStatus::Status => ex
|
||||
res.status = ex.code
|
||||
rescue StandardError => ex
|
||||
@logger.error(ex)
|
||||
res.set_error(ex, true)
|
||||
ensure
|
||||
if req.request_line
|
||||
if req.keep_alive? && res.keep_alive?
|
||||
req.fixup()
|
||||
end
|
||||
res.send_response(sock)
|
||||
server.access_log(@config, req, res)
|
||||
end
|
||||
end
|
||||
break if @http_version < "1.1"
|
||||
break unless req.keep_alive?
|
||||
break unless res.keep_alive?
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Services +req+ and fills in +res+
|
||||
|
||||
def service(req, res)
|
||||
if req.unparsed_uri == "*"
|
||||
if req.request_method == "OPTIONS"
|
||||
do_OPTIONS(req, res)
|
||||
raise HTTPStatus::OK
|
||||
end
|
||||
raise HTTPStatus::NotFound, "`#{req.unparsed_uri}' not found."
|
||||
end
|
||||
|
||||
servlet, options, script_name, path_info = search_servlet(req.path)
|
||||
raise HTTPStatus::NotFound, "`#{req.path}' not found." unless servlet
|
||||
req.script_name = script_name
|
||||
req.path_info = path_info
|
||||
si = servlet.get_instance(self, *options)
|
||||
@logger.debug(format("%s is invoked.", si.class.name))
|
||||
si.service(req, res)
|
||||
end
|
||||
|
||||
##
|
||||
# The default OPTIONS request handler says GET, HEAD, POST and OPTIONS
|
||||
# requests are allowed.
|
||||
|
||||
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]
|
||||
if servlet
|
||||
[ servlet, options, script_name, path_info ]
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Adds +server+ as a virtual host.
|
||||
|
||||
def virtual_host(server)
|
||||
@virtual_hosts << server
|
||||
@virtual_hosts = @virtual_hosts.sort_by{|s|
|
||||
num = 0
|
||||
num -= 4 if s[:BindAddress]
|
||||
num -= 2 if s[:Port]
|
||||
num -= 1 if s[:ServerName]
|
||||
num
|
||||
}
|
||||
end
|
||||
|
||||
##
|
||||
# Finds the appropriate virtual host to handle +req+
|
||||
|
||||
def lookup_server(req)
|
||||
@virtual_hosts.find{|s|
|
||||
(s[:BindAddress].nil? || req.addr[3] == s[:BindAddress]) &&
|
||||
(s[:Port].nil? || req.port == s[:Port]) &&
|
||||
((s[:ServerName].nil? || req.host == s[:ServerName]) ||
|
||||
(!s[:ServerAlias].nil? && s[:ServerAlias].find{|h| h === req.host}))
|
||||
}
|
||||
end
|
||||
|
||||
##
|
||||
# Logs +req+ and +res+ in the access logs. +config+ is used for the
|
||||
# server name.
|
||||
|
||||
def access_log(config, req, res)
|
||||
param = AccessLog::setup_params(config, req, res)
|
||||
@config[:AccessLog].each{|logger, fmt|
|
||||
logger << AccessLog::format(fmt+"\n", param)
|
||||
}
|
||||
end
|
||||
|
||||
##
|
||||
# Creates the HTTPRequest used when handling the HTTP
|
||||
# request. Can be overridden by subclasses.
|
||||
def create_request(with_webrick_config)
|
||||
HTTPRequest.new(with_webrick_config)
|
||||
end
|
||||
|
||||
##
|
||||
# Creates the HTTPResponse used when handling the HTTP
|
||||
# request. Can be overridden by subclasses.
|
||||
def create_response(with_webrick_config)
|
||||
HTTPResponse.new(with_webrick_config)
|
||||
end
|
||||
|
||||
##
|
||||
# Mount table for the path a servlet is mounted on in the directory space
|
||||
# of the server. Users of WEBrick can only access this indirectly via
|
||||
# WEBrick::HTTPServer#mount, WEBrick::HTTPServer#unmount and
|
||||
# WEBrick::HTTPServer#search_servlet
|
||||
|
||||
class MountTable # :nodoc:
|
||||
def initialize
|
||||
@tab = Hash.new
|
||||
compile
|
||||
end
|
||||
|
||||
def [](dir)
|
||||
dir = normalize(dir)
|
||||
@tab[dir]
|
||||
end
|
||||
|
||||
def []=(dir, val)
|
||||
dir = normalize(dir)
|
||||
@tab[dir] = val
|
||||
compile
|
||||
val
|
||||
end
|
||||
|
||||
def delete(dir)
|
||||
dir = normalize(dir)
|
||||
res = @tab.delete(dir)
|
||||
compile
|
||||
res
|
||||
end
|
||||
|
||||
def scan(path)
|
||||
@scanner =~ path
|
||||
[ $&, $' ]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compile
|
||||
k = @tab.keys
|
||||
k.sort!
|
||||
k.reverse!
|
||||
k.collect!{|path| Regexp.escape(path) }
|
||||
@scanner = Regexp.new("\\A(" + k.join("|") +")(?=/|\\z)")
|
||||
end
|
||||
|
||||
def normalize(dir)
|
||||
ret = dir ? dir.dup : ""
|
||||
ret.sub!(%r|/+\z|, "")
|
||||
ret
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,23 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# httpservlet.rb -- HTTPServlet Utility File
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: httpservlet.rb,v 1.21 2003/02/23 12:24:46 gotoyuzo Exp $
|
||||
|
||||
require_relative 'httpservlet/abstract'
|
||||
require_relative 'httpservlet/filehandler'
|
||||
require_relative 'httpservlet/cgihandler'
|
||||
require_relative 'httpservlet/erbhandler'
|
||||
require_relative 'httpservlet/prochandler'
|
||||
|
||||
module WEBrick
|
||||
module HTTPServlet
|
||||
FileHandler.add_handler("cgi", CGIHandler)
|
||||
FileHandler.add_handler("rhtml", ERBHandler)
|
||||
end
|
||||
end
|
|
@ -1,152 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# httpservlet.rb -- HTTPServlet Module
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2000 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: abstract.rb,v 1.24 2003/07/11 11:16:46 gotoyuzo Exp $
|
||||
|
||||
require_relative '../htmlutils'
|
||||
require_relative '../httputils'
|
||||
require_relative '../httpstatus'
|
||||
|
||||
module WEBrick
|
||||
module HTTPServlet
|
||||
class HTTPServletError < StandardError; end
|
||||
|
||||
##
|
||||
# AbstractServlet allows HTTP server modules to be reused across multiple
|
||||
# servers and allows encapsulation of functionality.
|
||||
#
|
||||
# By default a servlet will respond to GET, HEAD (through an alias to GET)
|
||||
# and OPTIONS requests.
|
||||
#
|
||||
# By default a new servlet is initialized for every request. A servlet
|
||||
# instance can be reused by overriding ::get_instance in the
|
||||
# AbstractServlet subclass.
|
||||
#
|
||||
# == A Simple Servlet
|
||||
#
|
||||
# class Simple < WEBrick::HTTPServlet::AbstractServlet
|
||||
# def do_GET request, response
|
||||
# status, content_type, body = do_stuff_with request
|
||||
#
|
||||
# response.status = status
|
||||
# response['Content-Type'] = content_type
|
||||
# response.body = body
|
||||
# end
|
||||
#
|
||||
# def do_stuff_with request
|
||||
# return 200, 'text/plain', 'you got a page'
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This servlet can be mounted on a server at a given path:
|
||||
#
|
||||
# server.mount '/simple', Simple
|
||||
#
|
||||
# == Servlet Configuration
|
||||
#
|
||||
# Servlets can be configured via initialize. The first argument is the
|
||||
# HTTP server the servlet is being initialized for.
|
||||
#
|
||||
# class Configurable < Simple
|
||||
# def initialize server, color, size
|
||||
# super server
|
||||
# @color = color
|
||||
# @size = size
|
||||
# end
|
||||
#
|
||||
# def do_stuff_with request
|
||||
# content = "<p " \
|
||||
# %q{style="color: #{@color}; font-size: #{@size}"} \
|
||||
# ">Hello, World!"
|
||||
#
|
||||
# return 200, "text/html", content
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This servlet must be provided two arguments at mount time:
|
||||
#
|
||||
# server.mount '/configurable', Configurable, 'red', '2em'
|
||||
|
||||
class AbstractServlet
|
||||
|
||||
##
|
||||
# Factory for servlet instances that will handle a request from +server+
|
||||
# using +options+ from the mount point. By default a new servlet
|
||||
# instance is created for every call.
|
||||
|
||||
def self.get_instance(server, *options)
|
||||
self.new(server, *options)
|
||||
end
|
||||
|
||||
##
|
||||
# Initializes a new servlet for +server+ using +options+ which are
|
||||
# stored as-is in +@options+. +@logger+ is also provided.
|
||||
|
||||
def initialize(server, *options)
|
||||
@server = @config = server
|
||||
@logger = @server[:Logger]
|
||||
@options = options
|
||||
end
|
||||
|
||||
##
|
||||
# Dispatches to a +do_+ method based on +req+ if such a method is
|
||||
# available. (+do_GET+ for a GET request). Raises a MethodNotAllowed
|
||||
# exception if the method is not implemented.
|
||||
|
||||
def service(req, res)
|
||||
method_name = "do_" + req.request_method.gsub(/-/, "_")
|
||||
if respond_to?(method_name)
|
||||
__send__(method_name, req, res)
|
||||
else
|
||||
raise HTTPStatus::MethodNotAllowed,
|
||||
"unsupported method `#{req.request_method}'."
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Raises a NotFound exception
|
||||
|
||||
def do_GET(req, res)
|
||||
raise HTTPStatus::NotFound, "not found."
|
||||
end
|
||||
|
||||
##
|
||||
# Dispatches to do_GET
|
||||
|
||||
def do_HEAD(req, res)
|
||||
do_GET(req, res)
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the allowed HTTP request methods
|
||||
|
||||
def do_OPTIONS(req, res)
|
||||
m = self.methods.grep(/\Ado_([A-Z]+)\z/) {$1}
|
||||
m.sort!
|
||||
res["allow"] = m.join(",")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
##
|
||||
# Redirects to a path ending in /
|
||||
|
||||
def redirect_to_directory_uri(req, res)
|
||||
if req.path[-1] != ?/
|
||||
location = WEBrick::HTTPUtils.escape_path(req.path + "/")
|
||||
if req.query_string && req.query_string.bytesize > 0
|
||||
location << "?" << req.query_string
|
||||
end
|
||||
res.set_redirect(HTTPStatus::MovedPermanently, location)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,47 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# cgi_runner.rb -- CGI launcher.
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2000 TAKAHASHI Masayoshi, GOTOU YUUZOU
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: cgi_runner.rb,v 1.9 2002/09/25 11:33:15 gotoyuzo Exp $
|
||||
|
||||
def sysread(io, size)
|
||||
buf = ""
|
||||
while size > 0
|
||||
tmp = io.sysread(size)
|
||||
buf << tmp
|
||||
size -= tmp.bytesize
|
||||
end
|
||||
return buf
|
||||
end
|
||||
|
||||
STDIN.binmode
|
||||
|
||||
len = sysread(STDIN, 8).to_i
|
||||
out = sysread(STDIN, len)
|
||||
STDOUT.reopen(File.open(out, "w"))
|
||||
|
||||
len = sysread(STDIN, 8).to_i
|
||||
err = sysread(STDIN, len)
|
||||
STDERR.reopen(File.open(err, "w"))
|
||||
|
||||
len = sysread(STDIN, 8).to_i
|
||||
dump = sysread(STDIN, len)
|
||||
hash = Marshal.restore(dump)
|
||||
ENV.keys.each{|name| ENV.delete(name) }
|
||||
hash.each{|k, v| ENV[k] = v if v }
|
||||
|
||||
dir = File::dirname(ENV["SCRIPT_FILENAME"])
|
||||
Dir::chdir dir
|
||||
|
||||
if ARGV[0]
|
||||
argv = ARGV.dup
|
||||
argv << ENV["SCRIPT_FILENAME"]
|
||||
exec(*argv)
|
||||
# NOTREACHED
|
||||
end
|
||||
exec ENV["SCRIPT_FILENAME"]
|
|
@ -1,126 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# cgihandler.rb -- CGIHandler Class
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: cgihandler.rb,v 1.27 2003/03/21 19:56:01 gotoyuzo Exp $
|
||||
|
||||
require 'rbconfig'
|
||||
require 'tempfile'
|
||||
require_relative '../config'
|
||||
require_relative 'abstract'
|
||||
|
||||
module WEBrick
|
||||
module HTTPServlet
|
||||
|
||||
##
|
||||
# Servlet for handling CGI scripts
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# server.mount('/cgi/my_script', WEBrick::HTTPServlet::CGIHandler,
|
||||
# '/path/to/my_script')
|
||||
|
||||
class CGIHandler < AbstractServlet
|
||||
Ruby = RbConfig.ruby # :nodoc:
|
||||
CGIRunner = "\"#{Ruby}\" \"#{WEBrick::Config::LIBDIR}/httpservlet/cgi_runner.rb\"" # :nodoc:
|
||||
CGIRunnerArray = [Ruby, "#{WEBrick::Config::LIBDIR}/httpservlet/cgi_runner.rb".freeze].freeze # :nodoc:
|
||||
|
||||
##
|
||||
# Creates a new CGI script servlet for the script at +name+
|
||||
|
||||
def initialize(server, name)
|
||||
super(server, name)
|
||||
@script_filename = name
|
||||
@tempdir = server[:TempDir]
|
||||
interpreter = server[:CGIInterpreter]
|
||||
if interpreter.is_a?(Array)
|
||||
@cgicmd = CGIRunnerArray + interpreter
|
||||
else
|
||||
@cgicmd = "#{CGIRunner} #{interpreter}"
|
||||
end
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
def do_GET(req, res)
|
||||
cgi_in = IO::popen(@cgicmd, "wb")
|
||||
cgi_out = Tempfile.new("webrick.cgiout.", @tempdir, mode: IO::BINARY)
|
||||
cgi_out.set_encoding("ASCII-8BIT")
|
||||
cgi_err = Tempfile.new("webrick.cgierr.", @tempdir, mode: IO::BINARY)
|
||||
cgi_err.set_encoding("ASCII-8BIT")
|
||||
begin
|
||||
cgi_in.sync = true
|
||||
meta = req.meta_vars
|
||||
meta["SCRIPT_FILENAME"] = @script_filename
|
||||
meta["PATH"] = @config[:CGIPathEnv]
|
||||
meta.delete("HTTP_PROXY")
|
||||
if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
|
||||
meta["SystemRoot"] = ENV["SystemRoot"]
|
||||
end
|
||||
dump = Marshal.dump(meta)
|
||||
|
||||
cgi_in.write("%8d" % cgi_out.path.bytesize)
|
||||
cgi_in.write(cgi_out.path)
|
||||
cgi_in.write("%8d" % cgi_err.path.bytesize)
|
||||
cgi_in.write(cgi_err.path)
|
||||
cgi_in.write("%8d" % dump.bytesize)
|
||||
cgi_in.write(dump)
|
||||
|
||||
req.body { |chunk| cgi_in.write(chunk) }
|
||||
ensure
|
||||
cgi_in.close
|
||||
status = $?.exitstatus
|
||||
sleep 0.1 if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
|
||||
data = cgi_out.read
|
||||
cgi_out.close(true)
|
||||
if errmsg = cgi_err.read
|
||||
if errmsg.bytesize > 0
|
||||
@logger.error("CGIHandler: #{@script_filename}:\n" + errmsg)
|
||||
end
|
||||
end
|
||||
cgi_err.close(true)
|
||||
end
|
||||
|
||||
if status != 0
|
||||
@logger.error("CGIHandler: #{@script_filename} exit with #{status}")
|
||||
end
|
||||
|
||||
data = "" unless data
|
||||
raw_header, body = data.split(/^[\xd\xa]+/, 2)
|
||||
raise HTTPStatus::InternalServerError,
|
||||
"Premature end of script headers: #{@script_filename}" if body.nil?
|
||||
|
||||
begin
|
||||
header = HTTPUtils::parse_header(raw_header)
|
||||
if /^(\d+)/ =~ header['status'][0]
|
||||
res.status = $1.to_i
|
||||
header.delete('status')
|
||||
end
|
||||
if header.has_key?('location')
|
||||
# RFC 3875 6.2.3, 6.2.4
|
||||
res.status = 302 unless (300...400) === res.status
|
||||
end
|
||||
if header.has_key?('set-cookie')
|
||||
header['set-cookie'].each{|k|
|
||||
res.cookies << Cookie.parse_set_cookie(k)
|
||||
}
|
||||
header.delete('set-cookie')
|
||||
end
|
||||
header.each{|key, val| res[key] = val.join(", ") }
|
||||
rescue => ex
|
||||
raise HTTPStatus::InternalServerError, ex.message
|
||||
end
|
||||
res.body = body
|
||||
end
|
||||
alias do_POST do_GET
|
||||
|
||||
# :startdoc:
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,88 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# erbhandler.rb -- ERBHandler Class
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: erbhandler.rb,v 1.25 2003/02/24 19:25:31 gotoyuzo Exp $
|
||||
|
||||
require_relative 'abstract'
|
||||
|
||||
require 'erb'
|
||||
|
||||
module WEBrick
|
||||
module HTTPServlet
|
||||
|
||||
##
|
||||
# ERBHandler evaluates an ERB file and returns the result. This handler
|
||||
# is automatically used if there are .rhtml files in a directory served by
|
||||
# the FileHandler.
|
||||
#
|
||||
# ERBHandler supports GET and POST methods.
|
||||
#
|
||||
# The ERB file is evaluated with the local variables +servlet_request+ and
|
||||
# +servlet_response+ which are a WEBrick::HTTPRequest and
|
||||
# WEBrick::HTTPResponse respectively.
|
||||
#
|
||||
# Example .rhtml file:
|
||||
#
|
||||
# Request to <%= servlet_request.request_uri %>
|
||||
#
|
||||
# Query params <%= servlet_request.query.inspect %>
|
||||
|
||||
class ERBHandler < AbstractServlet
|
||||
|
||||
##
|
||||
# Creates a new ERBHandler on +server+ that will evaluate and serve the
|
||||
# ERB file +name+
|
||||
|
||||
def initialize(server, name)
|
||||
super(server, name)
|
||||
@script_filename = name
|
||||
end
|
||||
|
||||
##
|
||||
# Handles GET requests
|
||||
|
||||
def do_GET(req, res)
|
||||
unless defined?(ERB)
|
||||
@logger.warn "#{self.class}: ERB not defined."
|
||||
raise HTTPStatus::Forbidden, "ERBHandler cannot work."
|
||||
end
|
||||
begin
|
||||
data = File.open(@script_filename, &:read)
|
||||
res.body = evaluate(ERB.new(data), req, res)
|
||||
res['content-type'] ||=
|
||||
HTTPUtils::mime_type(@script_filename, @config[:MimeTypes])
|
||||
rescue StandardError
|
||||
raise
|
||||
rescue Exception => ex
|
||||
@logger.error(ex)
|
||||
raise HTTPStatus::InternalServerError, ex.message
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Handles POST requests
|
||||
|
||||
alias do_POST do_GET
|
||||
|
||||
private
|
||||
|
||||
##
|
||||
# Evaluates +erb+ providing +servlet_request+ and +servlet_response+ as
|
||||
# local variables.
|
||||
|
||||
def evaluate(erb, servlet_request, servlet_response)
|
||||
Module.new.module_eval{
|
||||
servlet_request.meta_vars
|
||||
servlet_request.query
|
||||
erb.result(binding)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,552 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# filehandler.rb -- FileHandler Module
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
||||
# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: filehandler.rb,v 1.44 2003/06/07 01:34:51 gotoyuzo Exp $
|
||||
|
||||
require 'time'
|
||||
|
||||
require_relative '../htmlutils'
|
||||
require_relative '../httputils'
|
||||
require_relative '../httpstatus'
|
||||
|
||||
module WEBrick
|
||||
module HTTPServlet
|
||||
|
||||
##
|
||||
# Servlet for serving a single file. You probably want to use the
|
||||
# FileHandler servlet instead as it handles directories and fancy indexes.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# server.mount('/my_page.txt', WEBrick::HTTPServlet::DefaultFileHandler,
|
||||
# '/path/to/my_page.txt')
|
||||
#
|
||||
# This servlet handles If-Modified-Since and Range requests.
|
||||
|
||||
class DefaultFileHandler < AbstractServlet
|
||||
|
||||
##
|
||||
# Creates a DefaultFileHandler instance for the file at +local_path+.
|
||||
|
||||
def initialize(server, local_path)
|
||||
super(server, local_path)
|
||||
@local_path = local_path
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
def do_GET(req, res)
|
||||
st = File::stat(@local_path)
|
||||
mtime = st.mtime
|
||||
res['etag'] = sprintf("%x-%x-%x", st.ino, st.size, st.mtime.to_i)
|
||||
|
||||
if not_modified?(req, res, mtime, res['etag'])
|
||||
res.body = ''
|
||||
raise HTTPStatus::NotModified
|
||||
elsif req['range']
|
||||
make_partial_content(req, res, @local_path, st.size)
|
||||
raise HTTPStatus::PartialContent
|
||||
else
|
||||
mtype = HTTPUtils::mime_type(@local_path, @config[:MimeTypes])
|
||||
res['content-type'] = mtype
|
||||
res['content-length'] = st.size.to_s
|
||||
res['last-modified'] = mtime.httpdate
|
||||
res.body = File.open(@local_path, "rb")
|
||||
end
|
||||
end
|
||||
|
||||
def not_modified?(req, res, mtime, etag)
|
||||
if ir = req['if-range']
|
||||
begin
|
||||
if Time.httpdate(ir) >= mtime
|
||||
return true
|
||||
end
|
||||
rescue
|
||||
if HTTPUtils::split_header_value(ir).member?(res['etag'])
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (ims = req['if-modified-since']) && Time.parse(ims) >= mtime
|
||||
return true
|
||||
end
|
||||
|
||||
if (inm = req['if-none-match']) &&
|
||||
HTTPUtils::split_header_value(inm).member?(res['etag'])
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
# returns a lambda for webrick/httpresponse.rb send_body_proc
|
||||
def multipart_body(body, parts, boundary, mtype, filesize)
|
||||
lambda do |socket|
|
||||
begin
|
||||
begin
|
||||
first = parts.shift
|
||||
last = parts.shift
|
||||
socket.write(
|
||||
"--#{boundary}#{CRLF}" \
|
||||
"Content-Type: #{mtype}#{CRLF}" \
|
||||
"Content-Range: bytes #{first}-#{last}/#{filesize}#{CRLF}" \
|
||||
"#{CRLF}"
|
||||
)
|
||||
|
||||
begin
|
||||
IO.copy_stream(body, socket, last - first + 1, first)
|
||||
rescue NotImplementedError
|
||||
body.seek(first, IO::SEEK_SET)
|
||||
IO.copy_stream(body, socket, last - first + 1)
|
||||
end
|
||||
socket.write(CRLF)
|
||||
end while parts[0]
|
||||
socket.write("--#{boundary}--#{CRLF}")
|
||||
ensure
|
||||
body.close
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_partial_content(req, res, filename, filesize)
|
||||
mtype = HTTPUtils::mime_type(filename, @config[:MimeTypes])
|
||||
unless ranges = HTTPUtils::parse_range_header(req['range'])
|
||||
raise HTTPStatus::BadRequest,
|
||||
"Unrecognized range-spec: \"#{req['range']}\""
|
||||
end
|
||||
File.open(filename, "rb"){|io|
|
||||
if ranges.size > 1
|
||||
time = Time.now
|
||||
boundary = "#{time.sec}_#{time.usec}_#{Process::pid}"
|
||||
parts = []
|
||||
ranges.each {|range|
|
||||
prange = prepare_range(range, filesize)
|
||||
next if prange[0] < 0
|
||||
parts.concat(prange)
|
||||
}
|
||||
raise HTTPStatus::RequestRangeNotSatisfiable if parts.empty?
|
||||
res["content-type"] = "multipart/byteranges; boundary=#{boundary}"
|
||||
if req.http_version < '1.1'
|
||||
res['connection'] = 'close'
|
||||
else
|
||||
res.chunked = true
|
||||
end
|
||||
res.body = multipart_body(io.dup, parts, boundary, mtype, filesize)
|
||||
elsif range = ranges[0]
|
||||
first, last = prepare_range(range, filesize)
|
||||
raise HTTPStatus::RequestRangeNotSatisfiable if first < 0
|
||||
res['content-type'] = mtype
|
||||
res['content-range'] = "bytes #{first}-#{last}/#{filesize}"
|
||||
res['content-length'] = (last - first + 1).to_s
|
||||
res.body = io.dup
|
||||
else
|
||||
raise HTTPStatus::BadRequest
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def prepare_range(range, filesize)
|
||||
first = range.first < 0 ? filesize + range.first : range.first
|
||||
return -1, -1 if first < 0 || first >= filesize
|
||||
last = range.last < 0 ? filesize + range.last : range.last
|
||||
last = filesize - 1 if last >= filesize
|
||||
return first, last
|
||||
end
|
||||
|
||||
# :startdoc:
|
||||
end
|
||||
|
||||
##
|
||||
# Serves a directory including fancy indexing and a variety of other
|
||||
# options.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# server.mount('/assets', WEBrick::HTTPServlet::FileHandler,
|
||||
# '/path/to/assets')
|
||||
|
||||
class FileHandler < AbstractServlet
|
||||
HandlerTable = Hash.new # :nodoc:
|
||||
|
||||
##
|
||||
# Allow custom handling of requests for files with +suffix+ by class
|
||||
# +handler+
|
||||
|
||||
def self.add_handler(suffix, handler)
|
||||
HandlerTable[suffix] = handler
|
||||
end
|
||||
|
||||
##
|
||||
# Remove custom handling of requests for files with +suffix+
|
||||
|
||||
def self.remove_handler(suffix)
|
||||
HandlerTable.delete(suffix)
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a FileHandler servlet on +server+ that serves files starting
|
||||
# at directory +root+
|
||||
#
|
||||
# +options+ may be a Hash containing keys from
|
||||
# WEBrick::Config::FileHandler or +true+ or +false+.
|
||||
#
|
||||
# If +options+ is true or false then +:FancyIndexing+ is enabled or
|
||||
# disabled respectively.
|
||||
|
||||
def initialize(server, root, options={}, default=Config::FileHandler)
|
||||
@config = server.config
|
||||
@logger = @config[:Logger]
|
||||
@root = File.expand_path(root)
|
||||
if options == true || options == false
|
||||
options = { :FancyIndexing => options }
|
||||
end
|
||||
@options = default.dup.update(options)
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
def set_filesystem_encoding(str)
|
||||
enc = Encoding.find('filesystem')
|
||||
if enc == Encoding::US_ASCII
|
||||
str.b
|
||||
else
|
||||
str.dup.force_encoding(enc)
|
||||
end
|
||||
end
|
||||
|
||||
def service(req, res)
|
||||
# if this class is mounted on "/" and /~username is requested.
|
||||
# we're going to override path information before invoking service.
|
||||
if defined?(Etc) && @options[:UserDir] && req.script_name.empty?
|
||||
if %r|^(/~([^/]+))| =~ req.path_info
|
||||
script_name, user = $1, $2
|
||||
path_info = $'
|
||||
begin
|
||||
passwd = Etc::getpwnam(user)
|
||||
@root = File::join(passwd.dir, @options[:UserDir])
|
||||
req.script_name = script_name
|
||||
req.path_info = path_info
|
||||
rescue
|
||||
@logger.debug "#{self.class}#do_GET: getpwnam(#{user}) failed"
|
||||
end
|
||||
end
|
||||
end
|
||||
prevent_directory_traversal(req, res)
|
||||
super(req, res)
|
||||
end
|
||||
|
||||
def do_GET(req, res)
|
||||
unless exec_handler(req, res)
|
||||
set_dir_list(req, res)
|
||||
end
|
||||
end
|
||||
|
||||
def do_POST(req, res)
|
||||
unless exec_handler(req, res)
|
||||
raise HTTPStatus::NotFound, "`#{req.path}' not found."
|
||||
end
|
||||
end
|
||||
|
||||
def do_OPTIONS(req, res)
|
||||
unless exec_handler(req, res)
|
||||
super(req, res)
|
||||
end
|
||||
end
|
||||
|
||||
# ToDo
|
||||
# RFC2518: HTTP Extensions for Distributed Authoring -- WEBDAV
|
||||
#
|
||||
# PROPFIND PROPPATCH MKCOL DELETE PUT COPY MOVE
|
||||
# LOCK UNLOCK
|
||||
|
||||
# RFC3253: Versioning Extensions to WebDAV
|
||||
# (Web Distributed Authoring and Versioning)
|
||||
#
|
||||
# VERSION-CONTROL REPORT CHECKOUT CHECK_IN UNCHECKOUT
|
||||
# MKWORKSPACE UPDATE LABEL MERGE ACTIVITY
|
||||
|
||||
private
|
||||
|
||||
def trailing_pathsep?(path)
|
||||
# check for trailing path separator:
|
||||
# File.dirname("/aaaa/bbbb/") #=> "/aaaa")
|
||||
# File.dirname("/aaaa/bbbb/x") #=> "/aaaa/bbbb")
|
||||
# File.dirname("/aaaa/bbbb") #=> "/aaaa")
|
||||
# File.dirname("/aaaa/bbbbx") #=> "/aaaa")
|
||||
return File.dirname(path) != File.dirname(path+"x")
|
||||
end
|
||||
|
||||
def prevent_directory_traversal(req, res)
|
||||
# Preventing directory traversal on Windows platforms;
|
||||
# Backslashes (0x5c) in path_info are not interpreted as special
|
||||
# character in URI notation. So the value of path_info should be
|
||||
# normalize before accessing to the filesystem.
|
||||
|
||||
# dirty hack for filesystem encoding; in nature, File.expand_path
|
||||
# should not be used for path normalization. [Bug #3345]
|
||||
path = req.path_info.dup.force_encoding(Encoding.find("filesystem"))
|
||||
if trailing_pathsep?(req.path_info)
|
||||
# File.expand_path removes the trailing path separator.
|
||||
# Adding a character is a workaround to save it.
|
||||
# File.expand_path("/aaa/") #=> "/aaa"
|
||||
# File.expand_path("/aaa/" + "x") #=> "/aaa/x"
|
||||
expanded = File.expand_path(path + "x")
|
||||
expanded.chop! # remove trailing "x"
|
||||
else
|
||||
expanded = File.expand_path(path)
|
||||
end
|
||||
expanded.force_encoding(req.path_info.encoding)
|
||||
req.path_info = expanded
|
||||
end
|
||||
|
||||
def exec_handler(req, res)
|
||||
raise HTTPStatus::NotFound, "`#{req.path}' not found." unless @root
|
||||
if set_filename(req, res)
|
||||
handler = get_handler(req, res)
|
||||
call_callback(:HandlerCallback, req, res)
|
||||
h = handler.get_instance(@config, res.filename)
|
||||
h.service(req, res)
|
||||
return true
|
||||
end
|
||||
call_callback(:HandlerCallback, req, res)
|
||||
return false
|
||||
end
|
||||
|
||||
def get_handler(req, res)
|
||||
suffix1 = (/\.(\w+)\z/ =~ res.filename) && $1.downcase
|
||||
if /\.(\w+)\.([\w\-]+)\z/ =~ res.filename
|
||||
if @options[:AcceptableLanguages].include?($2.downcase)
|
||||
suffix2 = $1.downcase
|
||||
end
|
||||
end
|
||||
handler_table = @options[:HandlerTable]
|
||||
return handler_table[suffix1] || handler_table[suffix2] ||
|
||||
HandlerTable[suffix1] || HandlerTable[suffix2] ||
|
||||
DefaultFileHandler
|
||||
end
|
||||
|
||||
def set_filename(req, res)
|
||||
res.filename = @root
|
||||
path_info = req.path_info.scan(%r|/[^/]*|)
|
||||
|
||||
path_info.unshift("") # dummy for checking @root dir
|
||||
while base = path_info.first
|
||||
base = set_filesystem_encoding(base)
|
||||
break if base == "/"
|
||||
break unless File.directory?(File.expand_path(res.filename + base))
|
||||
shift_path_info(req, res, path_info)
|
||||
call_callback(:DirectoryCallback, req, res)
|
||||
end
|
||||
|
||||
if base = path_info.first
|
||||
base = set_filesystem_encoding(base)
|
||||
if base == "/"
|
||||
if file = search_index_file(req, res)
|
||||
shift_path_info(req, res, path_info, file)
|
||||
call_callback(:FileCallback, req, res)
|
||||
return true
|
||||
end
|
||||
shift_path_info(req, res, path_info)
|
||||
elsif file = search_file(req, res, base)
|
||||
shift_path_info(req, res, path_info, file)
|
||||
call_callback(:FileCallback, req, res)
|
||||
return true
|
||||
else
|
||||
raise HTTPStatus::NotFound, "`#{req.path}' not found."
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
def check_filename(req, res, name)
|
||||
if nondisclosure_name?(name) || windows_ambiguous_name?(name)
|
||||
@logger.warn("the request refers nondisclosure name `#{name}'.")
|
||||
raise HTTPStatus::NotFound, "`#{req.path}' not found."
|
||||
end
|
||||
end
|
||||
|
||||
def shift_path_info(req, res, path_info, base=nil)
|
||||
tmp = path_info.shift
|
||||
base = base || set_filesystem_encoding(tmp)
|
||||
req.path_info = path_info.join
|
||||
req.script_name << base
|
||||
res.filename = File.expand_path(res.filename + base)
|
||||
check_filename(req, res, File.basename(res.filename))
|
||||
end
|
||||
|
||||
def search_index_file(req, res)
|
||||
@config[:DirectoryIndex].each{|index|
|
||||
if file = search_file(req, res, "/"+index)
|
||||
return file
|
||||
end
|
||||
}
|
||||
return nil
|
||||
end
|
||||
|
||||
def search_file(req, res, basename)
|
||||
langs = @options[:AcceptableLanguages]
|
||||
path = res.filename + basename
|
||||
if File.file?(path)
|
||||
return basename
|
||||
elsif langs.size > 0
|
||||
req.accept_language.each{|lang|
|
||||
path_with_lang = path + ".#{lang}"
|
||||
if langs.member?(lang) && File.file?(path_with_lang)
|
||||
return basename + ".#{lang}"
|
||||
end
|
||||
}
|
||||
(langs - req.accept_language).each{|lang|
|
||||
path_with_lang = path + ".#{lang}"
|
||||
if File.file?(path_with_lang)
|
||||
return basename + ".#{lang}"
|
||||
end
|
||||
}
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
def call_callback(callback_name, req, res)
|
||||
if cb = @options[callback_name]
|
||||
cb.call(req, res)
|
||||
end
|
||||
end
|
||||
|
||||
def windows_ambiguous_name?(name)
|
||||
return true if /[. ]+\z/ =~ name
|
||||
return true if /::\$DATA\z/ =~ name
|
||||
return false
|
||||
end
|
||||
|
||||
def nondisclosure_name?(name)
|
||||
@options[:NondisclosureName].each{|pattern|
|
||||
if File.fnmatch(pattern, name, File::FNM_CASEFOLD)
|
||||
return true
|
||||
end
|
||||
}
|
||||
return false
|
||||
end
|
||||
|
||||
def set_dir_list(req, res)
|
||||
redirect_to_directory_uri(req, res)
|
||||
unless @options[:FancyIndexing]
|
||||
raise HTTPStatus::Forbidden, "no access permission to `#{req.path}'"
|
||||
end
|
||||
local_path = res.filename
|
||||
list = Dir::entries(local_path).collect{|name|
|
||||
next if name == "." || name == ".."
|
||||
next if nondisclosure_name?(name)
|
||||
next if windows_ambiguous_name?(name)
|
||||
st = (File::stat(File.join(local_path, name)) rescue nil)
|
||||
if st.nil?
|
||||
[ name, nil, -1 ]
|
||||
elsif st.directory?
|
||||
[ name + "/", st.mtime, -1 ]
|
||||
else
|
||||
[ name, st.mtime, st.size ]
|
||||
end
|
||||
}
|
||||
list.compact!
|
||||
|
||||
query = req.query
|
||||
|
||||
d0 = nil
|
||||
idx = nil
|
||||
%w[N M S].each_with_index do |q, i|
|
||||
if d = query.delete(q)
|
||||
idx ||= i
|
||||
d0 ||= d
|
||||
end
|
||||
end
|
||||
d0 ||= "A"
|
||||
idx ||= 0
|
||||
d1 = (d0 == "A") ? "D" : "A"
|
||||
|
||||
if d0 == "A"
|
||||
list.sort!{|a,b| a[idx] <=> b[idx] }
|
||||
else
|
||||
list.sort!{|a,b| b[idx] <=> a[idx] }
|
||||
end
|
||||
|
||||
namewidth = query["NameWidth"]
|
||||
if namewidth == "*"
|
||||
namewidth = nil
|
||||
elsif !namewidth or (namewidth = namewidth.to_i) < 2
|
||||
namewidth = 25
|
||||
end
|
||||
query = query.inject('') {|s, (k, v)| s << '&' << HTMLUtils::escape("#{k}=#{v}")}
|
||||
|
||||
type = "text/html"
|
||||
case enc = Encoding.find('filesystem')
|
||||
when Encoding::US_ASCII, Encoding::ASCII_8BIT
|
||||
else
|
||||
type << "; charset=\"#{enc.name}\""
|
||||
end
|
||||
res['content-type'] = type
|
||||
|
||||
title = "Index of #{HTMLUtils::escape(req.path)}"
|
||||
res.body = <<-_end_of_html_
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>#{title}</TITLE>
|
||||
<style type="text/css">
|
||||
<!--
|
||||
.name, .mtime { text-align: left; }
|
||||
.size { text-align: right; }
|
||||
td { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }
|
||||
table { border-collapse: collapse; }
|
||||
tr th { border-bottom: 2px groove; }
|
||||
//-->
|
||||
</style>
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<H1>#{title}</H1>
|
||||
_end_of_html_
|
||||
|
||||
res.body << "<TABLE width=\"100%\"><THEAD><TR>\n"
|
||||
res.body << "<TH class=\"name\"><A HREF=\"?N=#{d1}#{query}\">Name</A></TH>"
|
||||
res.body << "<TH class=\"mtime\"><A HREF=\"?M=#{d1}#{query}\">Last modified</A></TH>"
|
||||
res.body << "<TH class=\"size\"><A HREF=\"?S=#{d1}#{query}\">Size</A></TH>\n"
|
||||
res.body << "</TR></THEAD>\n"
|
||||
res.body << "<TBODY>\n"
|
||||
|
||||
query.sub!(/\A&/, '?')
|
||||
list.unshift [ "..", File::mtime(local_path+"/.."), -1 ]
|
||||
list.each{ |name, time, size|
|
||||
if name == ".."
|
||||
dname = "Parent Directory"
|
||||
elsif namewidth and name.size > namewidth
|
||||
dname = name[0...(namewidth - 2)] << '..'
|
||||
else
|
||||
dname = name
|
||||
end
|
||||
s = "<TR><TD class=\"name\"><A HREF=\"#{HTTPUtils::escape(name)}#{query if name.end_with?('/')}\">#{HTMLUtils::escape(dname)}</A></TD>"
|
||||
s << "<TD class=\"mtime\">" << (time ? time.strftime("%Y/%m/%d %H:%M") : "") << "</TD>"
|
||||
s << "<TD class=\"size\">" << (size >= 0 ? size.to_s : "-") << "</TD></TR>\n"
|
||||
res.body << s
|
||||
}
|
||||
res.body << "</TBODY></TABLE>"
|
||||
res.body << "<HR>"
|
||||
|
||||
res.body << <<-_end_of_html_
|
||||
<ADDRESS>
|
||||
#{HTMLUtils::escape(@config[:ServerSoftware])}<BR>
|
||||
at #{req.host}:#{req.port}
|
||||
</ADDRESS>
|
||||
</BODY>
|
||||
</HTML>
|
||||
_end_of_html_
|
||||
end
|
||||
|
||||
# :startdoc:
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,47 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# prochandler.rb -- ProcHandler Class
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: prochandler.rb,v 1.7 2002/09/21 12:23:42 gotoyuzo Exp $
|
||||
|
||||
require_relative 'abstract'
|
||||
|
||||
module WEBrick
|
||||
module HTTPServlet
|
||||
|
||||
##
|
||||
# Mounts a proc at a path that accepts a request and response.
|
||||
#
|
||||
# Instead of mounting this servlet with WEBrick::HTTPServer#mount use
|
||||
# WEBrick::HTTPServer#mount_proc:
|
||||
#
|
||||
# server.mount_proc '/' do |req, res|
|
||||
# res.body = 'it worked!'
|
||||
# res.status = 200
|
||||
# end
|
||||
|
||||
class ProcHandler < AbstractServlet
|
||||
# :stopdoc:
|
||||
def get_instance(server, *options)
|
||||
self
|
||||
end
|
||||
|
||||
def initialize(proc)
|
||||
@proc = proc
|
||||
end
|
||||
|
||||
def do_GET(request, response)
|
||||
@proc.call(request, response)
|
||||
end
|
||||
|
||||
alias do_POST do_GET
|
||||
# :startdoc:
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,194 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# httpstatus.rb -- HTTPStatus Class
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: httpstatus.rb,v 1.11 2003/03/24 20:18:55 gotoyuzo Exp $
|
||||
|
||||
require_relative 'accesslog'
|
||||
|
||||
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
|
||||
class << self
|
||||
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 # :nodoc:
|
||||
end
|
||||
|
||||
# Root of the HTTP info statuses
|
||||
class Info < Status; end
|
||||
# Root of the HTTP success 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
|
||||
|
||||
# HTTP status codes and descriptions
|
||||
StatusMessage = { # :nodoc:
|
||||
100 => 'Continue',
|
||||
101 => 'Switching Protocols',
|
||||
200 => 'OK',
|
||||
201 => 'Created',
|
||||
202 => 'Accepted',
|
||||
203 => 'Non-Authoritative Information',
|
||||
204 => 'No Content',
|
||||
205 => 'Reset Content',
|
||||
206 => 'Partial Content',
|
||||
207 => 'Multi-Status',
|
||||
300 => 'Multiple Choices',
|
||||
301 => 'Moved Permanently',
|
||||
302 => 'Found',
|
||||
303 => 'See Other',
|
||||
304 => 'Not Modified',
|
||||
305 => 'Use Proxy',
|
||||
307 => 'Temporary Redirect',
|
||||
400 => 'Bad Request',
|
||||
401 => 'Unauthorized',
|
||||
402 => 'Payment Required',
|
||||
403 => 'Forbidden',
|
||||
404 => 'Not Found',
|
||||
405 => 'Method Not Allowed',
|
||||
406 => 'Not Acceptable',
|
||||
407 => 'Proxy Authentication Required',
|
||||
408 => 'Request Timeout',
|
||||
409 => 'Conflict',
|
||||
410 => 'Gone',
|
||||
411 => 'Length Required',
|
||||
412 => 'Precondition Failed',
|
||||
413 => 'Request Entity Too Large',
|
||||
414 => 'Request-URI Too Large',
|
||||
415 => 'Unsupported Media Type',
|
||||
416 => 'Request Range Not Satisfiable',
|
||||
417 => 'Expectation Failed',
|
||||
422 => 'Unprocessable Entity',
|
||||
423 => 'Locked',
|
||||
424 => 'Failed Dependency',
|
||||
426 => 'Upgrade Required',
|
||||
428 => 'Precondition Required',
|
||||
429 => 'Too Many Requests',
|
||||
431 => 'Request Header Fields Too Large',
|
||||
451 => 'Unavailable For Legal Reasons',
|
||||
500 => 'Internal Server Error',
|
||||
501 => 'Not Implemented',
|
||||
502 => 'Bad Gateway',
|
||||
503 => 'Service Unavailable',
|
||||
504 => 'Gateway Timeout',
|
||||
505 => 'HTTP Version Not Supported',
|
||||
507 => 'Insufficient Storage',
|
||||
511 => 'Network Authentication Required',
|
||||
}
|
||||
|
||||
# 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
|
||||
err_name = message.gsub(/[ \-]/,'')
|
||||
|
||||
case code
|
||||
when 100...200; parent = Info
|
||||
when 200...300; parent = Success
|
||||
when 300...400; parent = Redirect
|
||||
when 400...500; parent = ClientError
|
||||
when 500...600; parent = ServerError
|
||||
end
|
||||
|
||||
const_set("RC_#{var_name}", code)
|
||||
err_class = Class.new(parent)
|
||||
err_class.instance_variable_set(:@code, code)
|
||||
err_class.instance_variable_set(:@reason_phrase, message)
|
||||
const_set(err_name, err_class)
|
||||
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
|
||||
|
||||
module_function :reason_phrase
|
||||
module_function :info?, :success?, :redirect?, :error?
|
||||
module_function :client_error?, :server_error?
|
||||
end
|
||||
end
|
|
@ -1,512 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# httputils.rb -- HTTPUtils Module
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: httputils.rb,v 1.34 2003/06/05 21:34:08 gotoyuzo Exp $
|
||||
|
||||
require 'socket'
|
||||
require 'tempfile'
|
||||
|
||||
module WEBrick
|
||||
CR = "\x0d" # :nodoc:
|
||||
LF = "\x0a" # :nodoc:
|
||||
CRLF = "\x0d\x0a" # :nodoc:
|
||||
|
||||
##
|
||||
# HTTPUtils provides utility methods for working with the HTTP protocol.
|
||||
#
|
||||
# This module is generally used internally by WEBrick
|
||||
|
||||
module HTTPUtils
|
||||
|
||||
##
|
||||
# Normalizes a request path. Raises an exception if the path cannot be
|
||||
# normalized.
|
||||
|
||||
def normalize_path(path)
|
||||
raise "abnormal path `#{path}'" if path[0] != ?/
|
||||
ret = path.dup
|
||||
|
||||
ret.gsub!(%r{/+}o, '/') # // => /
|
||||
while ret.sub!(%r'/\.(?:/|\Z)', '/'); end # /. => /
|
||||
while ret.sub!(%r'/(?!\.\./)[^/]+/\.\.(?:/|\Z)', '/'); end # /foo/.. => /foo
|
||||
|
||||
raise "abnormal path `#{path}'" if %r{/\.\.(/|\Z)} =~ ret
|
||||
ret
|
||||
end
|
||||
module_function :normalize_path
|
||||
|
||||
##
|
||||
# Default mime types
|
||||
|
||||
DefaultMimeTypes = {
|
||||
"ai" => "application/postscript",
|
||||
"asc" => "text/plain",
|
||||
"avi" => "video/x-msvideo",
|
||||
"bin" => "application/octet-stream",
|
||||
"bmp" => "image/bmp",
|
||||
"class" => "application/octet-stream",
|
||||
"cer" => "application/pkix-cert",
|
||||
"crl" => "application/pkix-crl",
|
||||
"crt" => "application/x-x509-ca-cert",
|
||||
#"crl" => "application/x-pkcs7-crl",
|
||||
"css" => "text/css",
|
||||
"dms" => "application/octet-stream",
|
||||
"doc" => "application/msword",
|
||||
"dvi" => "application/x-dvi",
|
||||
"eps" => "application/postscript",
|
||||
"etx" => "text/x-setext",
|
||||
"exe" => "application/octet-stream",
|
||||
"gif" => "image/gif",
|
||||
"htm" => "text/html",
|
||||
"html" => "text/html",
|
||||
"jpe" => "image/jpeg",
|
||||
"jpeg" => "image/jpeg",
|
||||
"jpg" => "image/jpeg",
|
||||
"js" => "application/javascript",
|
||||
"json" => "application/json",
|
||||
"lha" => "application/octet-stream",
|
||||
"lzh" => "application/octet-stream",
|
||||
"mjs" => "application/javascript",
|
||||
"mov" => "video/quicktime",
|
||||
"mpe" => "video/mpeg",
|
||||
"mpeg" => "video/mpeg",
|
||||
"mpg" => "video/mpeg",
|
||||
"pbm" => "image/x-portable-bitmap",
|
||||
"pdf" => "application/pdf",
|
||||
"pgm" => "image/x-portable-graymap",
|
||||
"png" => "image/png",
|
||||
"pnm" => "image/x-portable-anymap",
|
||||
"ppm" => "image/x-portable-pixmap",
|
||||
"ppt" => "application/vnd.ms-powerpoint",
|
||||
"ps" => "application/postscript",
|
||||
"qt" => "video/quicktime",
|
||||
"ras" => "image/x-cmu-raster",
|
||||
"rb" => "text/plain",
|
||||
"rd" => "text/plain",
|
||||
"rtf" => "application/rtf",
|
||||
"sgm" => "text/sgml",
|
||||
"sgml" => "text/sgml",
|
||||
"svg" => "image/svg+xml",
|
||||
"tif" => "image/tiff",
|
||||
"tiff" => "image/tiff",
|
||||
"txt" => "text/plain",
|
||||
"wasm" => "application/wasm",
|
||||
"xbm" => "image/x-xbitmap",
|
||||
"xhtml" => "text/html",
|
||||
"xls" => "application/vnd.ms-excel",
|
||||
"xml" => "text/xml",
|
||||
"xpm" => "image/x-xpixmap",
|
||||
"xwd" => "image/x-xwindowdump",
|
||||
"zip" => "application/zip",
|
||||
}
|
||||
|
||||
##
|
||||
# Loads Apache-compatible mime.types in +file+.
|
||||
|
||||
def load_mime_types(file)
|
||||
# note: +file+ may be a "| command" for now; some people may
|
||||
# rely on this, but currently we do not use this method by default.
|
||||
File.open(file){ |io|
|
||||
hash = Hash.new
|
||||
io.each{ |line|
|
||||
next if /^#/ =~ line
|
||||
line.chomp!
|
||||
mimetype, ext0 = line.split(/\s+/, 2)
|
||||
next unless ext0
|
||||
next if ext0.empty?
|
||||
ext0.split(/\s+/).each{ |ext| hash[ext] = mimetype }
|
||||
}
|
||||
hash
|
||||
}
|
||||
end
|
||||
module_function :load_mime_types
|
||||
|
||||
##
|
||||
# Returns the mime type of +filename+ from the list in +mime_tab+. If no
|
||||
# mime type was found application/octet-stream is returned.
|
||||
|
||||
def mime_type(filename, mime_tab)
|
||||
suffix1 = (/\.(\w+)$/ =~ filename && $1.downcase)
|
||||
suffix2 = (/\.(\w+)\.[\w\-]+$/ =~ filename && $1.downcase)
|
||||
mime_tab[suffix1] || mime_tab[suffix2] || "application/octet-stream"
|
||||
end
|
||||
module_function :mime_type
|
||||
|
||||
##
|
||||
# Parses an HTTP header +raw+ into a hash of header fields with an Array
|
||||
# of values.
|
||||
|
||||
def parse_header(raw)
|
||||
header = Hash.new([].freeze)
|
||||
field = nil
|
||||
raw.each_line{|line|
|
||||
case line
|
||||
when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):\s*(.*?)\s*\z/om
|
||||
field, value = $1, $2
|
||||
field.downcase!
|
||||
header[field] = [] unless header.has_key?(field)
|
||||
header[field] << value
|
||||
when /^\s+(.*?)\s*\z/om
|
||||
value = $1
|
||||
unless field
|
||||
raise HTTPStatus::BadRequest, "bad header '#{line}'."
|
||||
end
|
||||
header[field][-1] << " " << value
|
||||
else
|
||||
raise HTTPStatus::BadRequest, "bad header '#{line}'."
|
||||
end
|
||||
}
|
||||
header.each{|key, values|
|
||||
values.each(&:strip!)
|
||||
}
|
||||
header
|
||||
end
|
||||
module_function :parse_header
|
||||
|
||||
##
|
||||
# Splits a header value +str+ according to HTTP specification.
|
||||
|
||||
def split_header_value(str)
|
||||
str.scan(%r'\G((?:"(?:\\.|[^"])+?"|[^",]+)+)
|
||||
(?:,\s*|\Z)'xn).flatten
|
||||
end
|
||||
module_function :split_header_value
|
||||
|
||||
##
|
||||
# Parses a Range header value +ranges_specifier+
|
||||
|
||||
def parse_range_header(ranges_specifier)
|
||||
if /^bytes=(.*)/ =~ ranges_specifier
|
||||
byte_range_set = split_header_value($1)
|
||||
byte_range_set.collect{|range_spec|
|
||||
case range_spec
|
||||
when /^(\d+)-(\d+)/ then $1.to_i .. $2.to_i
|
||||
when /^(\d+)-/ then $1.to_i .. -1
|
||||
when /^-(\d+)/ then -($1.to_i) .. -1
|
||||
else return nil
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
module_function :parse_range_header
|
||||
|
||||
##
|
||||
# Parses q values in +value+ as used in Accept headers.
|
||||
|
||||
def parse_qvalues(value)
|
||||
tmp = []
|
||||
if value
|
||||
parts = value.split(/,\s*/)
|
||||
parts.each {|part|
|
||||
if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
|
||||
val = m[1]
|
||||
q = (m[2] or 1).to_f
|
||||
tmp.push([val, q])
|
||||
end
|
||||
}
|
||||
tmp = tmp.sort_by{|val, q| -q}
|
||||
tmp.collect!{|val, q| val}
|
||||
end
|
||||
return tmp
|
||||
end
|
||||
module_function :parse_qvalues
|
||||
|
||||
##
|
||||
# Removes quotes and escapes from +str+
|
||||
|
||||
def dequote(str)
|
||||
ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
|
||||
ret.gsub!(/\\(.)/, "\\1")
|
||||
ret
|
||||
end
|
||||
module_function :dequote
|
||||
|
||||
##
|
||||
# Quotes and escapes quotes in +str+
|
||||
|
||||
def quote(str)
|
||||
'"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
|
||||
end
|
||||
module_function :quote
|
||||
|
||||
##
|
||||
# Stores multipart form data. FormData objects are created when
|
||||
# WEBrick::HTTPUtils.parse_form_data is called.
|
||||
|
||||
class FormData < String
|
||||
EmptyRawHeader = [].freeze # :nodoc:
|
||||
EmptyHeader = {}.freeze # :nodoc:
|
||||
|
||||
##
|
||||
# The name of the form data part
|
||||
|
||||
attr_accessor :name
|
||||
|
||||
##
|
||||
# The filename of the form data part
|
||||
|
||||
attr_accessor :filename
|
||||
|
||||
attr_accessor :next_data # :nodoc:
|
||||
protected :next_data
|
||||
|
||||
##
|
||||
# Creates a new FormData object.
|
||||
#
|
||||
# +args+ is an Array of form data entries. One FormData will be created
|
||||
# for each entry.
|
||||
#
|
||||
# This is called by WEBrick::HTTPUtils.parse_form_data for you
|
||||
|
||||
def initialize(*args)
|
||||
@name = @filename = @next_data = nil
|
||||
if args.empty?
|
||||
@raw_header = []
|
||||
@header = nil
|
||||
super("")
|
||||
else
|
||||
@raw_header = EmptyRawHeader
|
||||
@header = EmptyHeader
|
||||
super(args.shift)
|
||||
unless args.empty?
|
||||
@next_data = self.class.new(*args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Retrieves the header at the first entry in +key+
|
||||
|
||||
def [](*key)
|
||||
begin
|
||||
@header[key[0].downcase].join(", ")
|
||||
rescue StandardError, NameError
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Adds +str+ to this FormData which may be the body, a header or a
|
||||
# header entry.
|
||||
#
|
||||
# This is called by WEBrick::HTTPUtils.parse_form_data for you
|
||||
|
||||
def <<(str)
|
||||
if @header
|
||||
super
|
||||
elsif str == CRLF
|
||||
@header = HTTPUtils::parse_header(@raw_header.join)
|
||||
if cd = self['content-disposition']
|
||||
if /\s+name="(.*?)"/ =~ cd then @name = $1 end
|
||||
if /\s+filename="(.*?)"/ =~ cd then @filename = $1 end
|
||||
end
|
||||
else
|
||||
@raw_header << str
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# Adds +data+ at the end of the chain of entries
|
||||
#
|
||||
# This is called by WEBrick::HTTPUtils.parse_form_data for you.
|
||||
|
||||
def append_data(data)
|
||||
tmp = self
|
||||
while tmp
|
||||
unless tmp.next_data
|
||||
tmp.next_data = data
|
||||
break
|
||||
end
|
||||
tmp = tmp.next_data
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# Yields each entry in this FormData
|
||||
|
||||
def each_data
|
||||
tmp = self
|
||||
while tmp
|
||||
next_data = tmp.next_data
|
||||
yield(tmp)
|
||||
tmp = next_data
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns all the FormData as an Array
|
||||
|
||||
def list
|
||||
ret = []
|
||||
each_data{|data|
|
||||
ret << data.to_s
|
||||
}
|
||||
ret
|
||||
end
|
||||
|
||||
##
|
||||
# A FormData will behave like an Array
|
||||
|
||||
alias :to_ary :list
|
||||
|
||||
##
|
||||
# This FormData's body
|
||||
|
||||
def to_s
|
||||
String.new(self)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Parses the query component of a URI in +str+
|
||||
|
||||
def parse_query(str)
|
||||
query = Hash.new
|
||||
if str
|
||||
str.split(/[&;]/).each{|x|
|
||||
next if x.empty?
|
||||
key, val = x.split(/=/,2)
|
||||
key = unescape_form(key)
|
||||
val = unescape_form(val.to_s)
|
||||
val = FormData.new(val)
|
||||
val.name = key
|
||||
if query.has_key?(key)
|
||||
query[key].append_data(val)
|
||||
next
|
||||
end
|
||||
query[key] = val
|
||||
}
|
||||
end
|
||||
query
|
||||
end
|
||||
module_function :parse_query
|
||||
|
||||
##
|
||||
# Parses form data in +io+ with the given +boundary+
|
||||
|
||||
def parse_form_data(io, boundary)
|
||||
boundary_regexp = /\A--#{Regexp.quote(boundary)}(--)?#{CRLF}\z/
|
||||
form_data = Hash.new
|
||||
return form_data unless io
|
||||
data = nil
|
||||
io.each_line{|line|
|
||||
if boundary_regexp =~ line
|
||||
if data
|
||||
data.chop!
|
||||
key = data.name
|
||||
if form_data.has_key?(key)
|
||||
form_data[key].append_data(data)
|
||||
else
|
||||
form_data[key] = data
|
||||
end
|
||||
end
|
||||
data = FormData.new
|
||||
next
|
||||
else
|
||||
if data
|
||||
data << line
|
||||
end
|
||||
end
|
||||
}
|
||||
return form_data
|
||||
end
|
||||
module_function :parse_form_data
|
||||
|
||||
#####
|
||||
|
||||
reserved = ';/?:@&=+$,'
|
||||
num = '0123456789'
|
||||
lowalpha = 'abcdefghijklmnopqrstuvwxyz'
|
||||
upalpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
mark = '-_.!~*\'()'
|
||||
unreserved = num + lowalpha + upalpha + mark
|
||||
control = (0x0..0x1f).collect{|c| c.chr }.join + "\x7f"
|
||||
space = " "
|
||||
delims = '<>#%"'
|
||||
unwise = '{}|\\^[]`'
|
||||
nonascii = (0x80..0xff).collect{|c| c.chr }.join
|
||||
|
||||
module_function
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
def _make_regex(str) /([#{Regexp.escape(str)}])/n end
|
||||
def _make_regex!(str) /([^#{Regexp.escape(str)}])/n end
|
||||
def _escape(str, regex)
|
||||
str = str.b
|
||||
str.gsub!(regex) {"%%%02X" % $1.ord}
|
||||
# %-escaped string should contain US-ASCII only
|
||||
str.force_encoding(Encoding::US_ASCII)
|
||||
end
|
||||
def _unescape(str, regex)
|
||||
str = str.b
|
||||
str.gsub!(regex) {$1.hex.chr}
|
||||
# encoding of %-unescaped string is unknown
|
||||
str
|
||||
end
|
||||
|
||||
UNESCAPED = _make_regex(control+space+delims+unwise+nonascii)
|
||||
UNESCAPED_FORM = _make_regex(reserved+control+delims+unwise+nonascii)
|
||||
NONASCII = _make_regex(nonascii)
|
||||
ESCAPED = /%([0-9a-fA-F]{2})/
|
||||
UNESCAPED_PCHAR = _make_regex!(unreserved+":@&=+$,")
|
||||
|
||||
# :startdoc:
|
||||
|
||||
##
|
||||
# Escapes HTTP reserved and unwise characters in +str+
|
||||
|
||||
def escape(str)
|
||||
_escape(str, UNESCAPED)
|
||||
end
|
||||
|
||||
##
|
||||
# Unescapes HTTP reserved and unwise characters in +str+
|
||||
|
||||
def unescape(str)
|
||||
_unescape(str, ESCAPED)
|
||||
end
|
||||
|
||||
##
|
||||
# Escapes form reserved characters in +str+
|
||||
|
||||
def escape_form(str)
|
||||
ret = _escape(str, UNESCAPED_FORM)
|
||||
ret.gsub!(/ /, "+")
|
||||
ret
|
||||
end
|
||||
|
||||
##
|
||||
# Unescapes form reserved characters in +str+
|
||||
|
||||
def unescape_form(str)
|
||||
_unescape(str.gsub(/\+/, " "), ESCAPED)
|
||||
end
|
||||
|
||||
##
|
||||
# Escapes path +str+
|
||||
|
||||
def escape_path(str)
|
||||
result = ""
|
||||
str.scan(%r{/([^/]*)}).each{|i|
|
||||
result << "/" << _escape(i[0], UNESCAPED_PCHAR)
|
||||
}
|
||||
return result
|
||||
end
|
||||
|
||||
##
|
||||
# Escapes 8 bit characters in +str+
|
||||
|
||||
def escape8bit(str)
|
||||
_escape(str, NONASCII)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,76 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# HTTPVersion.rb -- presentation of HTTP version
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: httpversion.rb,v 1.5 2002/09/21 12:23:37 gotoyuzo Exp $
|
||||
|
||||
module WEBrick
|
||||
|
||||
##
|
||||
# Represents an HTTP protocol version
|
||||
|
||||
class HTTPVersion
|
||||
include Comparable
|
||||
|
||||
##
|
||||
# The major protocol version number
|
||||
|
||||
attr_accessor :major
|
||||
|
||||
##
|
||||
# The minor protocol version number
|
||||
|
||||
attr_accessor :minor
|
||||
|
||||
##
|
||||
# Converts +version+ into an HTTPVersion
|
||||
|
||||
def self.convert(version)
|
||||
version.is_a?(self) ? version : new(version)
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a new HTTPVersion from +version+.
|
||||
|
||||
def initialize(version)
|
||||
case version
|
||||
when HTTPVersion
|
||||
@major, @minor = version.major, version.minor
|
||||
when String
|
||||
if /^(\d+)\.(\d+)$/ =~ version
|
||||
@major, @minor = $1.to_i, $2.to_i
|
||||
end
|
||||
end
|
||||
if @major.nil? || @minor.nil?
|
||||
raise ArgumentError,
|
||||
format("cannot convert %s into %s", version.class, self.class)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Compares this version with +other+ according to the HTTP specification
|
||||
# rules.
|
||||
|
||||
def <=>(other)
|
||||
unless other.is_a?(self.class)
|
||||
other = self.class.new(other)
|
||||
end
|
||||
if (ret = @major <=> other.major) == 0
|
||||
return @minor <=> other.minor
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
##
|
||||
# The HTTP version as show in the HTTP request and response. For example,
|
||||
# "1.1"
|
||||
|
||||
def to_s
|
||||
format("%d.%d", @major, @minor)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,156 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# log.rb -- Log Class
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: log.rb,v 1.26 2002/10/06 17:06:10 gotoyuzo Exp $
|
||||
|
||||
module WEBrick
|
||||
|
||||
##
|
||||
# A generic logging class
|
||||
|
||||
class BasicLog
|
||||
|
||||
# Fatal log level which indicates a server crash
|
||||
|
||||
FATAL = 1
|
||||
|
||||
# Error log level which indicates a recoverable error
|
||||
|
||||
ERROR = 2
|
||||
|
||||
# Warning log level which indicates a possible problem
|
||||
|
||||
WARN = 3
|
||||
|
||||
# Information log level which indicates possibly useful information
|
||||
|
||||
INFO = 4
|
||||
|
||||
# Debugging error level for messages used in server development or
|
||||
# debugging
|
||||
|
||||
DEBUG = 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
|
||||
when String
|
||||
@log = File.open(log_file, "a+")
|
||||
@log.sync = true
|
||||
@opened = true
|
||||
when NilClass
|
||||
@log = $stderr
|
||||
else
|
||||
@log = log_file # requires "<<". (see BasicLog#log)
|
||||
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
|
||||
@log << 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)
|
||||
if arg.is_a?(Exception)
|
||||
"#{arg.class}: #{AccessLog.escape(arg.message)}\n\t" <<
|
||||
arg.backtrace.join("\n\t") << "\n"
|
||||
elsif arg.respond_to?(:to_str)
|
||||
AccessLog.escape(arg.to_str)
|
||||
else
|
||||
arg.inspect
|
||||
end
|
||||
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
|
||||
super(level, tmp)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,381 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# server.rb -- GenericServer Class
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: server.rb,v 1.62 2003/07/22 19:20:43 gotoyuzo Exp $
|
||||
|
||||
require 'socket'
|
||||
require_relative 'config'
|
||||
require_relative 'log'
|
||||
|
||||
module WEBrick
|
||||
|
||||
##
|
||||
# Server error exception
|
||||
|
||||
class ServerError < StandardError; end
|
||||
|
||||
##
|
||||
# Base server class
|
||||
|
||||
class SimpleServer
|
||||
|
||||
##
|
||||
# A SimpleServer only yields when you start it
|
||||
|
||||
def SimpleServer.start
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# A generic module for daemonizing a process
|
||||
|
||||
class Daemon
|
||||
|
||||
##
|
||||
# Performs the standard operations for daemonizing a process. Runs a
|
||||
# block, if given.
|
||||
|
||||
def Daemon.start
|
||||
Process.daemon
|
||||
File.umask(0)
|
||||
yield if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Base TCP server class. You must subclass GenericServer and provide a #run
|
||||
# method.
|
||||
|
||||
class GenericServer
|
||||
|
||||
##
|
||||
# The server status. One of :Stop, :Running or :Shutdown
|
||||
|
||||
attr_reader :status
|
||||
|
||||
##
|
||||
# The server configuration
|
||||
|
||||
attr_reader :config
|
||||
|
||||
##
|
||||
# The server logger. This is independent from the HTTP access log.
|
||||
|
||||
attr_reader :logger
|
||||
|
||||
##
|
||||
# Tokens control the number of outstanding clients. The
|
||||
# <code>:MaxClients</code> configuration sets this.
|
||||
|
||||
attr_reader :tokens
|
||||
|
||||
##
|
||||
# Sockets listening for connections.
|
||||
|
||||
attr_reader :listeners
|
||||
|
||||
##
|
||||
# Creates a new generic server from +config+. The default configuration
|
||||
# comes from +default+.
|
||||
|
||||
def initialize(config={}, default=Config::General)
|
||||
@config = default.dup.update(config)
|
||||
@status = :Stop
|
||||
@config[:Logger] ||= Log::new
|
||||
@logger = @config[:Logger]
|
||||
|
||||
@tokens = Thread::SizedQueue.new(@config[:MaxClients])
|
||||
@config[:MaxClients].times{ @tokens.push(nil) }
|
||||
|
||||
webrickv = WEBrick::VERSION
|
||||
rubyv = "#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
|
||||
@logger.info("WEBrick #{webrickv}")
|
||||
@logger.info("ruby #{rubyv}")
|
||||
|
||||
@listeners = []
|
||||
@shutdown_pipe = nil
|
||||
unless @config[:DoNotListen]
|
||||
raise ArgumentError, "Port must an integer" unless @config[:Port].to_s == @config[:Port].to_i.to_s
|
||||
|
||||
@config[:Port] = @config[:Port].to_i
|
||||
if @config[:Listen]
|
||||
warn(":Listen option is deprecated; use GenericServer#listen", uplevel: 1)
|
||||
end
|
||||
listen(@config[:BindAddress], @config[:Port])
|
||||
if @config[:Port] == 0
|
||||
@config[:Port] = @listeners[0].addr[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Retrieves +key+ from the configuration
|
||||
|
||||
def [](key)
|
||||
@config[key]
|
||||
end
|
||||
|
||||
##
|
||||
# Adds listeners from +address+ and +port+ to the server. See
|
||||
# WEBrick::Utils::create_listeners for details.
|
||||
|
||||
def listen(address, port)
|
||||
@listeners += Utils::create_listeners(address, port)
|
||||
end
|
||||
|
||||
##
|
||||
# Starts the server and runs the +block+ for each connection. This method
|
||||
# does not return until the server is stopped from a signal handler or
|
||||
# another thread using #stop or #shutdown.
|
||||
#
|
||||
# If the block raises a subclass of StandardError the exception is logged
|
||||
# and ignored. If an IOError or Errno::EBADF exception is raised the
|
||||
# exception is ignored. If an Exception subclass is raised the exception
|
||||
# is logged and re-raised which stops the server.
|
||||
#
|
||||
# To completely shut down a server call #shutdown from ensure:
|
||||
#
|
||||
# server = WEBrick::GenericServer.new
|
||||
# # or WEBrick::HTTPServer.new
|
||||
#
|
||||
# begin
|
||||
# server.start
|
||||
# ensure
|
||||
# server.shutdown
|
||||
# end
|
||||
|
||||
def start(&block)
|
||||
raise ServerError, "already started." if @status != :Stop
|
||||
server_type = @config[:ServerType] || SimpleServer
|
||||
|
||||
setup_shutdown_pipe
|
||||
|
||||
server_type.start{
|
||||
@logger.info \
|
||||
"#{self.class}#start: pid=#{$$} port=#{@config[:Port]}"
|
||||
@status = :Running
|
||||
call_callback(:StartCallback)
|
||||
|
||||
shutdown_pipe = @shutdown_pipe
|
||||
|
||||
thgroup = ThreadGroup.new
|
||||
begin
|
||||
while @status == :Running
|
||||
begin
|
||||
sp = shutdown_pipe[0]
|
||||
if svrs = IO.select([sp, *@listeners])
|
||||
if svrs[0].include? sp
|
||||
# swallow shutdown pipe
|
||||
buf = String.new
|
||||
nil while String ===
|
||||
sp.read_nonblock([sp.nread, 8].max, buf, exception: false)
|
||||
break
|
||||
end
|
||||
svrs[0].each{|svr|
|
||||
@tokens.pop # blocks while no token is there.
|
||||
if sock = accept_client(svr)
|
||||
unless config[:DoNotReverseLookup].nil?
|
||||
sock.do_not_reverse_lookup = !!config[:DoNotReverseLookup]
|
||||
end
|
||||
th = start_thread(sock, &block)
|
||||
th[:WEBrickThread] = true
|
||||
thgroup.add(th)
|
||||
else
|
||||
@tokens.push(nil)
|
||||
end
|
||||
}
|
||||
end
|
||||
rescue Errno::EBADF, Errno::ENOTSOCK, IOError => ex
|
||||
# if the listening socket was closed in GenericServer#shutdown,
|
||||
# IO::select raise it.
|
||||
rescue StandardError => ex
|
||||
msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
|
||||
@logger.error msg
|
||||
rescue Exception => ex
|
||||
@logger.fatal ex
|
||||
raise
|
||||
end
|
||||
end
|
||||
ensure
|
||||
cleanup_shutdown_pipe(shutdown_pipe)
|
||||
cleanup_listener
|
||||
@status = :Shutdown
|
||||
@logger.info "going to shutdown ..."
|
||||
thgroup.list.each{|th| th.join if th[:WEBrickThread] }
|
||||
call_callback(:StopCallback)
|
||||
@logger.info "#{self.class}#start done."
|
||||
@status = :Stop
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
##
|
||||
# Stops the server from accepting new connections.
|
||||
|
||||
def stop
|
||||
if @status == :Running
|
||||
@status = :Shutdown
|
||||
end
|
||||
|
||||
alarm_shutdown_pipe {|f| f.write_nonblock("\0")}
|
||||
end
|
||||
|
||||
##
|
||||
# Shuts down the server and all listening sockets. New listeners must be
|
||||
# provided to restart the server.
|
||||
|
||||
def shutdown
|
||||
stop
|
||||
|
||||
alarm_shutdown_pipe(&:close)
|
||||
end
|
||||
|
||||
##
|
||||
# You must subclass GenericServer and implement \#run which accepts a TCP
|
||||
# client socket
|
||||
|
||||
def run(sock)
|
||||
@logger.fatal "run() must be provided by user."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
##
|
||||
# Accepts a TCP client socket from the TCP server socket +svr+ and returns
|
||||
# the client socket.
|
||||
|
||||
def accept_client(svr)
|
||||
case sock = svr.to_io.accept_nonblock(exception: false)
|
||||
when :wait_readable
|
||||
nil
|
||||
else
|
||||
if svr.respond_to?(:start_immediately)
|
||||
sock = OpenSSL::SSL::SSLSocket.new(sock, ssl_context)
|
||||
sock.sync_close = true
|
||||
# we cannot do OpenSSL::SSL::SSLSocket#accept here because
|
||||
# a slow client can prevent us from accepting connections
|
||||
# from other clients
|
||||
end
|
||||
sock
|
||||
end
|
||||
rescue Errno::ECONNRESET, Errno::ECONNABORTED,
|
||||
Errno::EPROTO, Errno::EINVAL
|
||||
nil
|
||||
rescue StandardError => ex
|
||||
msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
|
||||
@logger.error msg
|
||||
nil
|
||||
end
|
||||
|
||||
##
|
||||
# Starts a server thread for the client socket +sock+ that runs the given
|
||||
# +block+.
|
||||
#
|
||||
# Sets the socket to the <code>:WEBrickSocket</code> thread local variable
|
||||
# in the thread.
|
||||
#
|
||||
# If any errors occur in the block they are logged and handled.
|
||||
|
||||
def start_thread(sock, &block)
|
||||
Thread.start{
|
||||
begin
|
||||
Thread.current[:WEBrickSocket] = sock
|
||||
begin
|
||||
addr = sock.peeraddr
|
||||
@logger.debug "accept: #{addr[3]}:#{addr[1]}"
|
||||
rescue SocketError
|
||||
@logger.debug "accept: <address unknown>"
|
||||
raise
|
||||
end
|
||||
if sock.respond_to?(:sync_close=) && @config[:SSLStartImmediately]
|
||||
WEBrick::Utils.timeout(@config[:RequestTimeout]) do
|
||||
begin
|
||||
sock.accept # OpenSSL::SSL::SSLSocket#accept
|
||||
rescue Errno::ECONNRESET, Errno::ECONNABORTED,
|
||||
Errno::EPROTO, Errno::EINVAL
|
||||
Thread.exit
|
||||
end
|
||||
end
|
||||
end
|
||||
call_callback(:AcceptCallback, sock)
|
||||
block ? block.call(sock) : run(sock)
|
||||
rescue Errno::ENOTCONN
|
||||
@logger.debug "Errno::ENOTCONN raised"
|
||||
rescue ServerError => ex
|
||||
msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
|
||||
@logger.error msg
|
||||
rescue Exception => ex
|
||||
@logger.error ex
|
||||
ensure
|
||||
@tokens.push(nil)
|
||||
Thread.current[:WEBrickSocket] = nil
|
||||
if addr
|
||||
@logger.debug "close: #{addr[3]}:#{addr[1]}"
|
||||
else
|
||||
@logger.debug "close: <address unknown>"
|
||||
end
|
||||
sock.close
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
##
|
||||
# Calls the callback +callback_name+ from the configuration with +args+
|
||||
|
||||
def call_callback(callback_name, *args)
|
||||
@config[callback_name]&.call(*args)
|
||||
end
|
||||
|
||||
def setup_shutdown_pipe
|
||||
return @shutdown_pipe ||= IO.pipe
|
||||
end
|
||||
|
||||
def cleanup_shutdown_pipe(shutdown_pipe)
|
||||
@shutdown_pipe = nil
|
||||
shutdown_pipe&.each(&:close)
|
||||
end
|
||||
|
||||
def alarm_shutdown_pipe
|
||||
_, pipe = @shutdown_pipe # another thread may modify @shutdown_pipe.
|
||||
if pipe
|
||||
if !pipe.closed?
|
||||
begin
|
||||
yield pipe
|
||||
rescue IOError # closed by another thread.
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup_listener
|
||||
@listeners.each{|s|
|
||||
if @logger.debug?
|
||||
addr = s.addr
|
||||
@logger.debug("close TCPSocket(#{addr[2]}, #{addr[1]})")
|
||||
end
|
||||
begin
|
||||
s.shutdown
|
||||
rescue Errno::ENOTCONN
|
||||
# when `Errno::ENOTCONN: Socket is not connected' on some platforms,
|
||||
# call #close instead of #shutdown.
|
||||
# (ignore @config[:ShutdownSocketWithoutClose])
|
||||
s.close
|
||||
else
|
||||
unless @config[:ShutdownSocketWithoutClose]
|
||||
s.close
|
||||
end
|
||||
end
|
||||
}
|
||||
@listeners.clear
|
||||
end
|
||||
end # end of GenericServer
|
||||
end
|
|
@ -1,215 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# ssl.rb -- SSL/TLS enhancement for GenericServer
|
||||
#
|
||||
# Copyright (c) 2003 GOTOU Yuuzou All rights reserved.
|
||||
#
|
||||
# $Id$
|
||||
|
||||
require 'webrick'
|
||||
require 'openssl'
|
||||
|
||||
module WEBrick
|
||||
module Config
|
||||
svrsoft = General[:ServerSoftware]
|
||||
osslv = ::OpenSSL::OPENSSL_VERSION.split[1]
|
||||
|
||||
##
|
||||
# Default SSL server configuration.
|
||||
#
|
||||
# WEBrick can automatically create a self-signed certificate if
|
||||
# <code>:SSLCertName</code> is set. For more information on the various
|
||||
# SSL options see OpenSSL::SSL::SSLContext.
|
||||
#
|
||||
# :ServerSoftware ::
|
||||
# The server software name used in the Server: header.
|
||||
# :SSLEnable :: false,
|
||||
# Enable SSL for this server. Defaults to false.
|
||||
# :SSLCertificate ::
|
||||
# The SSL certificate for the server.
|
||||
# :SSLPrivateKey ::
|
||||
# The SSL private key for the server certificate.
|
||||
# :SSLClientCA :: nil,
|
||||
# Array of certificates that will be sent to the client.
|
||||
# :SSLExtraChainCert :: nil,
|
||||
# Array of certificates that will be added to the certificate chain
|
||||
# :SSLCACertificateFile :: nil,
|
||||
# Path to a CA certificate file
|
||||
# :SSLCACertificatePath :: nil,
|
||||
# Path to a directory containing CA certificates
|
||||
# :SSLCertificateStore :: nil,
|
||||
# OpenSSL::X509::Store used for certificate validation of the client
|
||||
# :SSLTmpDhCallback :: nil,
|
||||
# Callback invoked when DH parameters are required.
|
||||
# :SSLVerifyClient ::
|
||||
# Sets whether the client is verified. This defaults to VERIFY_NONE
|
||||
# which is typical for an HTTPS server.
|
||||
# :SSLVerifyDepth ::
|
||||
# Number of CA certificates to walk when verifying a certificate chain
|
||||
# :SSLVerifyCallback ::
|
||||
# Custom certificate verification callback
|
||||
# :SSLServerNameCallback::
|
||||
# Custom servername indication callback
|
||||
# :SSLTimeout ::
|
||||
# Maximum session lifetime
|
||||
# :SSLOptions ::
|
||||
# Various SSL options
|
||||
# :SSLCiphers ::
|
||||
# Ciphers to be used
|
||||
# :SSLStartImmediately ::
|
||||
# Immediately start SSL upon connection? Defaults to true
|
||||
# :SSLCertName ::
|
||||
# SSL certificate name. Must be set to enable automatic certificate
|
||||
# creation.
|
||||
# :SSLCertComment ::
|
||||
# Comment used during automatic certificate creation.
|
||||
|
||||
SSL = {
|
||||
:ServerSoftware => "#{svrsoft} OpenSSL/#{osslv}",
|
||||
:SSLEnable => false,
|
||||
:SSLCertificate => nil,
|
||||
:SSLPrivateKey => nil,
|
||||
:SSLClientCA => nil,
|
||||
:SSLExtraChainCert => nil,
|
||||
:SSLCACertificateFile => nil,
|
||||
:SSLCACertificatePath => nil,
|
||||
:SSLCertificateStore => nil,
|
||||
:SSLTmpDhCallback => nil,
|
||||
:SSLVerifyClient => ::OpenSSL::SSL::VERIFY_NONE,
|
||||
:SSLVerifyDepth => nil,
|
||||
:SSLVerifyCallback => nil, # custom verification
|
||||
:SSLTimeout => nil,
|
||||
:SSLOptions => nil,
|
||||
:SSLCiphers => nil,
|
||||
:SSLStartImmediately => true,
|
||||
# Must specify if you use auto generated certificate.
|
||||
:SSLCertName => nil,
|
||||
:SSLCertComment => "Generated by Ruby/OpenSSL"
|
||||
}
|
||||
General.update(SSL)
|
||||
end
|
||||
|
||||
module Utils
|
||||
##
|
||||
# Creates a self-signed certificate with the given number of +bits+,
|
||||
# the issuer +cn+ and a +comment+ to be stored in the certificate.
|
||||
|
||||
def create_self_signed_cert(bits, cn, comment)
|
||||
rsa = OpenSSL::PKey::RSA.new(bits){|p, n|
|
||||
case p
|
||||
when 0; $stderr.putc "." # BN_generate_prime
|
||||
when 1; $stderr.putc "+" # BN_generate_prime
|
||||
when 2; $stderr.putc "*" # searching good prime,
|
||||
# n = #of try,
|
||||
# but also data from BN_generate_prime
|
||||
when 3; $stderr.putc "\n" # found good prime, n==0 - p, n==1 - q,
|
||||
# but also data from BN_generate_prime
|
||||
else; $stderr.putc "*" # BN_generate_prime
|
||||
end
|
||||
}
|
||||
cert = OpenSSL::X509::Certificate.new
|
||||
cert.version = 2
|
||||
cert.serial = 1
|
||||
name = (cn.kind_of? String) ? OpenSSL::X509::Name.parse(cn)
|
||||
: OpenSSL::X509::Name.new(cn)
|
||||
cert.subject = name
|
||||
cert.issuer = name
|
||||
cert.not_before = Time.now
|
||||
cert.not_after = Time.now + (365*24*60*60)
|
||||
cert.public_key = rsa.public_key
|
||||
|
||||
ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
|
||||
ef.issuer_certificate = cert
|
||||
cert.extensions = [
|
||||
ef.create_extension("basicConstraints","CA:FALSE"),
|
||||
ef.create_extension("keyUsage", "keyEncipherment, digitalSignature, keyAgreement, dataEncipherment"),
|
||||
ef.create_extension("subjectKeyIdentifier", "hash"),
|
||||
ef.create_extension("extendedKeyUsage", "serverAuth"),
|
||||
ef.create_extension("nsComment", comment),
|
||||
]
|
||||
aki = ef.create_extension("authorityKeyIdentifier",
|
||||
"keyid:always,issuer:always")
|
||||
cert.add_extension(aki)
|
||||
cert.sign(rsa, "SHA256")
|
||||
|
||||
return [ cert, rsa ]
|
||||
end
|
||||
module_function :create_self_signed_cert
|
||||
end
|
||||
|
||||
##
|
||||
#--
|
||||
# Updates WEBrick::GenericServer with SSL functionality
|
||||
|
||||
class GenericServer
|
||||
|
||||
##
|
||||
# SSL context for the server when run in SSL mode
|
||||
|
||||
def ssl_context # :nodoc:
|
||||
@ssl_context ||= begin
|
||||
if @config[:SSLEnable]
|
||||
ssl_context = setup_ssl_context(@config)
|
||||
@logger.info("\n" + @config[:SSLCertificate].to_text)
|
||||
ssl_context
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
undef listen
|
||||
|
||||
##
|
||||
# Updates +listen+ to enable SSL when the SSL configuration is active.
|
||||
|
||||
def listen(address, port) # :nodoc:
|
||||
listeners = Utils::create_listeners(address, port)
|
||||
if @config[:SSLEnable]
|
||||
listeners.collect!{|svr|
|
||||
ssvr = ::OpenSSL::SSL::SSLServer.new(svr, ssl_context)
|
||||
ssvr.start_immediately = @config[:SSLStartImmediately]
|
||||
ssvr
|
||||
}
|
||||
end
|
||||
@listeners += listeners
|
||||
setup_shutdown_pipe
|
||||
end
|
||||
|
||||
##
|
||||
# Sets up an SSL context for +config+
|
||||
|
||||
def setup_ssl_context(config) # :nodoc:
|
||||
unless config[:SSLCertificate]
|
||||
cn = config[:SSLCertName]
|
||||
comment = config[:SSLCertComment]
|
||||
cert, key = Utils::create_self_signed_cert(2048, cn, comment)
|
||||
config[:SSLCertificate] = cert
|
||||
config[:SSLPrivateKey] = key
|
||||
end
|
||||
ctx = OpenSSL::SSL::SSLContext.new
|
||||
ctx.key = config[:SSLPrivateKey]
|
||||
ctx.cert = config[:SSLCertificate]
|
||||
ctx.client_ca = config[:SSLClientCA]
|
||||
ctx.extra_chain_cert = config[:SSLExtraChainCert]
|
||||
ctx.ca_file = config[:SSLCACertificateFile]
|
||||
ctx.ca_path = config[:SSLCACertificatePath]
|
||||
ctx.cert_store = config[:SSLCertificateStore]
|
||||
ctx.tmp_dh_callback = config[:SSLTmpDhCallback]
|
||||
ctx.verify_mode = config[:SSLVerifyClient]
|
||||
ctx.verify_depth = config[:SSLVerifyDepth]
|
||||
ctx.verify_callback = config[:SSLVerifyCallback]
|
||||
ctx.servername_cb = config[:SSLServerNameCallback] || proc { |args| ssl_servername_callback(*args) }
|
||||
ctx.timeout = config[:SSLTimeout]
|
||||
ctx.options = config[:SSLOptions]
|
||||
ctx.ciphers = config[:SSLCiphers]
|
||||
ctx
|
||||
end
|
||||
|
||||
##
|
||||
# ServerNameIndication callback
|
||||
|
||||
def ssl_servername_callback(sslsocket, hostname = nil)
|
||||
# default
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,265 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# utils.rb -- Miscellaneous utilities
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: utils.rb,v 1.10 2003/02/16 22:22:54 gotoyuzo Exp $
|
||||
|
||||
require 'socket'
|
||||
require 'io/nonblock'
|
||||
require 'etc'
|
||||
|
||||
module WEBrick
|
||||
module Utils
|
||||
##
|
||||
# Sets IO operations on +io+ to be non-blocking
|
||||
def set_non_blocking(io)
|
||||
io.nonblock = true if io.respond_to?(:nonblock=)
|
||||
end
|
||||
module_function :set_non_blocking
|
||||
|
||||
##
|
||||
# Sets the close on exec flag for +io+
|
||||
def set_close_on_exec(io)
|
||||
io.close_on_exec = true if io.respond_to?(:close_on_exec=)
|
||||
end
|
||||
module_function :set_close_on_exec
|
||||
|
||||
##
|
||||
# Changes the process's uid and gid to the ones of +user+
|
||||
def su(user)
|
||||
if pw = Etc.getpwnam(user)
|
||||
Process::initgroups(user, pw.gid)
|
||||
Process::Sys::setgid(pw.gid)
|
||||
Process::Sys::setuid(pw.uid)
|
||||
else
|
||||
warn("WEBrick::Utils::su doesn't work on this platform", uplevel: 1)
|
||||
end
|
||||
end
|
||||
module_function :su
|
||||
|
||||
##
|
||||
# The server hostname
|
||||
def getservername
|
||||
Socket::gethostname
|
||||
end
|
||||
module_function :getservername
|
||||
|
||||
##
|
||||
# Creates TCP server sockets bound to +address+:+port+ and returns them.
|
||||
#
|
||||
# It will create IPV4 and IPV6 sockets on all interfaces.
|
||||
def create_listeners(address, port)
|
||||
unless port
|
||||
raise ArgumentError, "must specify port"
|
||||
end
|
||||
sockets = Socket.tcp_server_sockets(address, port)
|
||||
sockets = sockets.map {|s|
|
||||
s.autoclose = false
|
||||
ts = TCPServer.for_fd(s.fileno)
|
||||
s.close
|
||||
ts
|
||||
}
|
||||
return sockets
|
||||
end
|
||||
module_function :create_listeners
|
||||
|
||||
##
|
||||
# Characters used to generate random strings
|
||||
RAND_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||
"0123456789" +
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
##
|
||||
# Generates a random string of length +len+
|
||||
def random_string(len)
|
||||
rand_max = RAND_CHARS.bytesize
|
||||
ret = ""
|
||||
len.times{ ret << RAND_CHARS[rand(rand_max)] }
|
||||
ret
|
||||
end
|
||||
module_function :random_string
|
||||
|
||||
###########
|
||||
|
||||
require "timeout"
|
||||
require "singleton"
|
||||
|
||||
##
|
||||
# Class used to manage timeout handlers across multiple threads.
|
||||
#
|
||||
# Timeout handlers should be managed by using the class methods which are
|
||||
# synchronized.
|
||||
#
|
||||
# id = TimeoutHandler.register(10, Timeout::Error)
|
||||
# begin
|
||||
# sleep 20
|
||||
# puts 'foo'
|
||||
# ensure
|
||||
# TimeoutHandler.cancel(id)
|
||||
# end
|
||||
#
|
||||
# will raise Timeout::Error
|
||||
#
|
||||
# id = TimeoutHandler.register(10, Timeout::Error)
|
||||
# begin
|
||||
# sleep 5
|
||||
# puts 'foo'
|
||||
# ensure
|
||||
# TimeoutHandler.cancel(id)
|
||||
# end
|
||||
#
|
||||
# will print 'foo'
|
||||
#
|
||||
class TimeoutHandler
|
||||
include Singleton
|
||||
|
||||
##
|
||||
# Mutex used to synchronize access across threads
|
||||
TimeoutMutex = Thread::Mutex.new # :nodoc:
|
||||
|
||||
##
|
||||
# Registers a new timeout handler
|
||||
#
|
||||
# +time+:: Timeout in seconds
|
||||
# +exception+:: Exception to raise when timeout elapsed
|
||||
def TimeoutHandler.register(seconds, exception)
|
||||
at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + seconds
|
||||
instance.register(Thread.current, at, exception)
|
||||
end
|
||||
|
||||
##
|
||||
# Cancels the timeout handler +id+
|
||||
def TimeoutHandler.cancel(id)
|
||||
instance.cancel(Thread.current, id)
|
||||
end
|
||||
|
||||
def self.terminate
|
||||
instance.terminate
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a new TimeoutHandler. You should use ::register and ::cancel
|
||||
# instead of creating the timeout handler directly.
|
||||
def initialize
|
||||
TimeoutMutex.synchronize{
|
||||
@timeout_info = Hash.new
|
||||
}
|
||||
@queue = Thread::Queue.new
|
||||
@watcher = nil
|
||||
end
|
||||
|
||||
# :nodoc:
|
||||
private \
|
||||
def watch
|
||||
to_interrupt = []
|
||||
while true
|
||||
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
wakeup = nil
|
||||
to_interrupt.clear
|
||||
TimeoutMutex.synchronize{
|
||||
@timeout_info.each {|thread, ary|
|
||||
next unless ary
|
||||
ary.each{|info|
|
||||
time, exception = *info
|
||||
if time < now
|
||||
to_interrupt.push [thread, info.object_id, exception]
|
||||
elsif !wakeup || time < wakeup
|
||||
wakeup = time
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
to_interrupt.each {|arg| interrupt(*arg)}
|
||||
if !wakeup
|
||||
@queue.pop
|
||||
elsif (wakeup -= now) > 0
|
||||
begin
|
||||
(th = Thread.start {@queue.pop}).join(wakeup)
|
||||
ensure
|
||||
th&.kill&.join
|
||||
end
|
||||
end
|
||||
@queue.clear
|
||||
end
|
||||
end
|
||||
|
||||
# :nodoc:
|
||||
private \
|
||||
def watcher
|
||||
(w = @watcher)&.alive? and return w # usual case
|
||||
TimeoutMutex.synchronize{
|
||||
(w = @watcher)&.alive? and next w # pathological check
|
||||
@watcher = Thread.start(&method(:watch))
|
||||
}
|
||||
end
|
||||
|
||||
##
|
||||
# Interrupts the timeout handler +id+ and raises +exception+
|
||||
def interrupt(thread, id, exception)
|
||||
if cancel(thread, id) && thread.alive?
|
||||
thread.raise(exception, "execution timeout")
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Registers a new timeout handler
|
||||
#
|
||||
# +time+:: Timeout in seconds
|
||||
# +exception+:: Exception to raise when timeout elapsed
|
||||
def register(thread, time, exception)
|
||||
info = nil
|
||||
TimeoutMutex.synchronize{
|
||||
(@timeout_info[thread] ||= []) << (info = [time, exception])
|
||||
}
|
||||
@queue.push nil
|
||||
watcher
|
||||
return info.object_id
|
||||
end
|
||||
|
||||
##
|
||||
# Cancels the timeout handler +id+
|
||||
def cancel(thread, id)
|
||||
TimeoutMutex.synchronize{
|
||||
if ary = @timeout_info[thread]
|
||||
ary.delete_if{|info| info.object_id == id }
|
||||
if ary.empty?
|
||||
@timeout_info.delete(thread)
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
}
|
||||
end
|
||||
|
||||
##
|
||||
def terminate
|
||||
TimeoutMutex.synchronize{
|
||||
@timeout_info.clear
|
||||
@watcher&.kill&.join
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Executes the passed block and raises +exception+ if execution takes more
|
||||
# than +seconds+.
|
||||
#
|
||||
# If +seconds+ is zero or nil, simply executes the block
|
||||
def timeout(seconds, exception=Timeout::Error)
|
||||
return yield if seconds.nil? or seconds.zero?
|
||||
# raise ThreadError, "timeout within critical session" if Thread.critical
|
||||
id = TimeoutHandler.register(seconds, exception)
|
||||
begin
|
||||
yield(seconds)
|
||||
ensure
|
||||
TimeoutHandler.cancel(id)
|
||||
end
|
||||
end
|
||||
module_function :timeout
|
||||
end
|
||||
end
|
|
@ -1,18 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# version.rb -- version and release date
|
||||
#
|
||||
# Author: IPR -- Internet Programming with Ruby -- writers
|
||||
# Copyright (c) 2000 TAKAHASHI Masayoshi, GOTOU YUUZOU
|
||||
# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
|
||||
# reserved.
|
||||
#
|
||||
# $IPR: version.rb,v 1.74 2003/07/22 19:20:43 gotoyuzo Exp $
|
||||
|
||||
module WEBrick
|
||||
|
||||
##
|
||||
# The WEBrick version
|
||||
|
||||
VERSION = "1.7.0"
|
||||
end
|
|
@ -1 +0,0 @@
|
|||
this file should not be published.
|
|
@ -1,148 +0,0 @@
|
|||
# coding: US-ASCII
|
||||
# frozen_string_literal: false
|
||||
require_relative "utils"
|
||||
require "webrick"
|
||||
require "test/unit"
|
||||
|
||||
class TestWEBrickCGI < Test::Unit::TestCase
|
||||
CRLF = "\r\n"
|
||||
|
||||
def teardown
|
||||
WEBrick::Utils::TimeoutHandler.terminate
|
||||
super
|
||||
end
|
||||
|
||||
def test_cgi
|
||||
TestWEBrick.start_cgi_server{|server, addr, port, log|
|
||||
http = Net::HTTP.new(addr, port)
|
||||
req = Net::HTTP::Get.new("/webrick.cgi")
|
||||
http.request(req){|res| assert_equal("/webrick.cgi", res.body, log.call)}
|
||||
req = Net::HTTP::Get.new("/webrick.cgi/path/info")
|
||||
http.request(req){|res| assert_equal("/path/info", res.body, log.call)}
|
||||
req = Net::HTTP::Get.new("/webrick.cgi/%3F%3F%3F?foo=bar")
|
||||
http.request(req){|res| assert_equal("/???", res.body, log.call)}
|
||||
unless RUBY_PLATFORM =~ /mswin|mingw|cygwin|bccwin32|java/
|
||||
# Path info of res.body is passed via ENV.
|
||||
# ENV[] returns different value on Windows depending on locale.
|
||||
req = Net::HTTP::Get.new("/webrick.cgi/%A4%DB%A4%B2/%A4%DB%A4%B2")
|
||||
http.request(req){|res|
|
||||
assert_equal("/\xA4\xDB\xA4\xB2/\xA4\xDB\xA4\xB2", res.body, log.call)}
|
||||
end
|
||||
req = Net::HTTP::Get.new("/webrick.cgi?a=1;a=2;b=x")
|
||||
http.request(req){|res| assert_equal("a=1, a=2, b=x", res.body, log.call)}
|
||||
req = Net::HTTP::Get.new("/webrick.cgi?a=1&a=2&b=x")
|
||||
http.request(req){|res| assert_equal("a=1, a=2, b=x", res.body, log.call)}
|
||||
|
||||
req = Net::HTTP::Post.new("/webrick.cgi?a=x;a=y;b=1")
|
||||
req["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
http.request(req, "a=1;a=2;b=x"){|res|
|
||||
assert_equal("a=1, a=2, b=x", res.body, log.call)}
|
||||
req = Net::HTTP::Post.new("/webrick.cgi?a=x&a=y&b=1")
|
||||
req["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
http.request(req, "a=1&a=2&b=x"){|res|
|
||||
assert_equal("a=1, a=2, b=x", res.body, log.call)}
|
||||
req = Net::HTTP::Get.new("/")
|
||||
http.request(req){|res|
|
||||
ary = res.body.lines.to_a
|
||||
assert_match(%r{/$}, ary[0], log.call)
|
||||
assert_match(%r{/webrick.cgi$}, ary[1], log.call)
|
||||
}
|
||||
|
||||
req = Net::HTTP::Get.new("/webrick.cgi")
|
||||
req["Cookie"] = "CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001"
|
||||
http.request(req){|res|
|
||||
assert_equal(
|
||||
"CUSTOMER=WILE_E_COYOTE\nPART_NUMBER=ROCKET_LAUNCHER_0001\n",
|
||||
res.body, log.call)
|
||||
}
|
||||
|
||||
req = Net::HTTP::Get.new("/webrick.cgi")
|
||||
cookie = %{$Version="1"; }
|
||||
cookie << %{Customer="WILE_E_COYOTE"; $Path="/acme"; }
|
||||
cookie << %{Part_Number="Rocket_Launcher_0001"; $Path="/acme"; }
|
||||
cookie << %{Shipping="FedEx"; $Path="/acme"}
|
||||
req["Cookie"] = cookie
|
||||
http.request(req){|res|
|
||||
assert_equal("Customer=WILE_E_COYOTE, Shipping=FedEx",
|
||||
res["Set-Cookie"], log.call)
|
||||
assert_equal("Customer=WILE_E_COYOTE\n" +
|
||||
"Part_Number=Rocket_Launcher_0001\n" +
|
||||
"Shipping=FedEx\n", res.body, log.call)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def test_bad_request
|
||||
log_tester = lambda {|log, access_log|
|
||||
assert_match(/BadRequest/, log.join)
|
||||
}
|
||||
TestWEBrick.start_cgi_server({}, log_tester) {|server, addr, port, log|
|
||||
sock = TCPSocket.new(addr, port)
|
||||
begin
|
||||
sock << "POST /webrick.cgi HTTP/1.0" << CRLF
|
||||
sock << "Content-Type: application/x-www-form-urlencoded" << CRLF
|
||||
sock << "Content-Length: 1024" << CRLF
|
||||
sock << CRLF
|
||||
sock << "a=1&a=2&b=x"
|
||||
sock.close_write
|
||||
assert_match(%r{\AHTTP/\d.\d 400 Bad Request}, sock.read, log.call)
|
||||
ensure
|
||||
sock.close
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def test_cgi_env
|
||||
TestWEBrick.start_cgi_server do |server, addr, port, log|
|
||||
http = Net::HTTP.new(addr, port)
|
||||
req = Net::HTTP::Get.new("/webrick.cgi/dumpenv")
|
||||
req['proxy'] = 'http://example.com/'
|
||||
req['hello'] = 'world'
|
||||
http.request(req) do |res|
|
||||
env = Marshal.load(res.body)
|
||||
assert_equal 'world', env['HTTP_HELLO']
|
||||
assert_not_operator env, :include?, 'HTTP_PROXY'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
CtrlSeq = [0x7f, *(1..31)].pack("C*").gsub(/\s+/, '')
|
||||
CtrlPat = /#{Regexp.quote(CtrlSeq)}/o
|
||||
DumpPat = /#{Regexp.quote(CtrlSeq.dump[1...-1])}/o
|
||||
|
||||
def test_bad_uri
|
||||
log_tester = lambda {|log, access_log|
|
||||
assert_equal(1, log.length)
|
||||
assert_match(/ERROR bad URI/, log[0])
|
||||
}
|
||||
TestWEBrick.start_cgi_server({}, log_tester) {|server, addr, port, log|
|
||||
res = TCPSocket.open(addr, port) {|sock|
|
||||
sock << "GET /#{CtrlSeq}#{CRLF}#{CRLF}"
|
||||
sock.close_write
|
||||
sock.read
|
||||
}
|
||||
assert_match(%r{\AHTTP/\d.\d 400 Bad Request}, res)
|
||||
s = log.call.each_line.grep(/ERROR bad URI/)[0]
|
||||
assert_match(DumpPat, s)
|
||||
assert_not_match(CtrlPat, s)
|
||||
}
|
||||
end
|
||||
|
||||
def test_bad_header
|
||||
log_tester = lambda {|log, access_log|
|
||||
assert_equal(1, log.length)
|
||||
assert_match(/ERROR bad header/, log[0])
|
||||
}
|
||||
TestWEBrick.start_cgi_server({}, log_tester) {|server, addr, port, log|
|
||||
res = TCPSocket.open(addr, port) {|sock|
|
||||
sock << "GET / HTTP/1.0#{CRLF}#{CtrlSeq}#{CRLF}#{CRLF}"
|
||||
sock.close_write
|
||||
sock.read
|
||||
}
|
||||
assert_match(%r{\AHTTP/\d.\d 400 Bad Request}, res)
|
||||
s = log.call.each_line.grep(/ERROR bad header/)[0]
|
||||
assert_match(DumpPat, s)
|
||||
assert_not_match(CtrlPat, s)
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,17 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "test/unit"
|
||||
require "webrick/config"
|
||||
|
||||
class TestWEBrickConfig < Test::Unit::TestCase
|
||||
def test_server_name_default
|
||||
config = WEBrick::Config::General.dup
|
||||
assert_equal(false, config.key?(:ServerName))
|
||||
assert_equal(WEBrick::Utils.getservername, config[:ServerName])
|
||||
assert_equal(true, config.key?(:ServerName))
|
||||
end
|
||||
|
||||
def test_server_name_set_nil
|
||||
config = WEBrick::Config::General.dup.update(ServerName: nil)
|
||||
assert_equal(nil, config[:ServerName])
|
||||
end
|
||||
end
|
|
@ -1,141 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "test/unit"
|
||||
require "webrick/cookie"
|
||||
|
||||
class TestWEBrickCookie < Test::Unit::TestCase
|
||||
def test_new
|
||||
cookie = WEBrick::Cookie.new("foo","bar")
|
||||
assert_equal("foo", cookie.name)
|
||||
assert_equal("bar", cookie.value)
|
||||
assert_equal("foo=bar", cookie.to_s)
|
||||
end
|
||||
|
||||
def test_time
|
||||
cookie = WEBrick::Cookie.new("foo","bar")
|
||||
t = 1000000000
|
||||
cookie.max_age = t
|
||||
assert_match(t.to_s, cookie.to_s)
|
||||
|
||||
cookie = WEBrick::Cookie.new("foo","bar")
|
||||
t = Time.at(1000000000)
|
||||
cookie.expires = t
|
||||
assert_equal(Time, cookie.expires.class)
|
||||
assert_equal(t, cookie.expires)
|
||||
ts = t.httpdate
|
||||
cookie.expires = ts
|
||||
assert_equal(Time, cookie.expires.class)
|
||||
assert_equal(t, cookie.expires)
|
||||
assert_match(ts, cookie.to_s)
|
||||
end
|
||||
|
||||
def test_parse
|
||||
data = ""
|
||||
data << '$Version="1"; '
|
||||
data << 'Customer="WILE_E_COYOTE"; $Path="/acme"; '
|
||||
data << 'Part_Number="Rocket_Launcher_0001"; $Path="/acme"; '
|
||||
data << 'Shipping="FedEx"; $Path="/acme"'
|
||||
cookies = WEBrick::Cookie.parse(data)
|
||||
assert_equal(3, cookies.size)
|
||||
assert_equal(1, cookies[0].version)
|
||||
assert_equal("Customer", cookies[0].name)
|
||||
assert_equal("WILE_E_COYOTE", cookies[0].value)
|
||||
assert_equal("/acme", cookies[0].path)
|
||||
assert_equal(1, cookies[1].version)
|
||||
assert_equal("Part_Number", cookies[1].name)
|
||||
assert_equal("Rocket_Launcher_0001", cookies[1].value)
|
||||
assert_equal(1, cookies[2].version)
|
||||
assert_equal("Shipping", cookies[2].name)
|
||||
assert_equal("FedEx", cookies[2].value)
|
||||
|
||||
data = "hoge=moge; __div__session=9865ecfd514be7f7"
|
||||
cookies = WEBrick::Cookie.parse(data)
|
||||
assert_equal(2, cookies.size)
|
||||
assert_equal(0, cookies[0].version)
|
||||
assert_equal("hoge", cookies[0].name)
|
||||
assert_equal("moge", cookies[0].value)
|
||||
assert_equal("__div__session", cookies[1].name)
|
||||
assert_equal("9865ecfd514be7f7", cookies[1].value)
|
||||
|
||||
# don't allow ,-separator
|
||||
data = "hoge=moge, __div__session=9865ecfd514be7f7"
|
||||
cookies = WEBrick::Cookie.parse(data)
|
||||
assert_equal(1, cookies.size)
|
||||
assert_equal(0, cookies[0].version)
|
||||
assert_equal("hoge", cookies[0].name)
|
||||
assert_equal("moge, __div__session=9865ecfd514be7f7", cookies[0].value)
|
||||
end
|
||||
|
||||
def test_parse_no_whitespace
|
||||
data = [
|
||||
'$Version="1"; ',
|
||||
'Customer="WILE_E_COYOTE";$Path="/acme";', # no SP between cookie-string
|
||||
'Part_Number="Rocket_Launcher_0001";$Path="/acme";', # no SP between cookie-string
|
||||
'Shipping="FedEx";$Path="/acme"'
|
||||
].join
|
||||
cookies = WEBrick::Cookie.parse(data)
|
||||
assert_equal(1, cookies.size)
|
||||
end
|
||||
|
||||
def test_parse_too_much_whitespaces
|
||||
# According to RFC6265,
|
||||
# cookie-string = cookie-pair *( ";" SP cookie-pair )
|
||||
# So single 0x20 is needed after ';'. We allow multiple spaces here for
|
||||
# compatibility with older WEBrick versions.
|
||||
data = [
|
||||
'$Version="1"; ',
|
||||
'Customer="WILE_E_COYOTE";$Path="/acme"; ', # no SP between cookie-string
|
||||
'Part_Number="Rocket_Launcher_0001";$Path="/acme"; ', # no SP between cookie-string
|
||||
'Shipping="FedEx";$Path="/acme"'
|
||||
].join
|
||||
cookies = WEBrick::Cookie.parse(data)
|
||||
assert_equal(3, cookies.size)
|
||||
end
|
||||
|
||||
def test_parse_set_cookie
|
||||
data = %(Customer="WILE_E_COYOTE"; Version="1"; Path="/acme")
|
||||
cookie = WEBrick::Cookie.parse_set_cookie(data)
|
||||
assert_equal("Customer", cookie.name)
|
||||
assert_equal("WILE_E_COYOTE", cookie.value)
|
||||
assert_equal(1, cookie.version)
|
||||
assert_equal("/acme", cookie.path)
|
||||
|
||||
data = %(Shipping="FedEx"; Version="1"; Path="/acme"; Secure)
|
||||
cookie = WEBrick::Cookie.parse_set_cookie(data)
|
||||
assert_equal("Shipping", cookie.name)
|
||||
assert_equal("FedEx", cookie.value)
|
||||
assert_equal(1, cookie.version)
|
||||
assert_equal("/acme", cookie.path)
|
||||
assert_equal(true, cookie.secure)
|
||||
end
|
||||
|
||||
def test_parse_set_cookies
|
||||
data = %(Shipping="FedEx"; Version="1"; Path="/acme"; Secure)
|
||||
data << %(, CUSTOMER=WILE_E_COYOTE; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT; path=/; Secure)
|
||||
data << %(, name="Aaron"; Version="1"; path="/acme")
|
||||
cookies = WEBrick::Cookie.parse_set_cookies(data)
|
||||
assert_equal(3, cookies.length)
|
||||
|
||||
fed_ex = cookies.find { |c| c.name == 'Shipping' }
|
||||
assert_not_nil(fed_ex)
|
||||
assert_equal("Shipping", fed_ex.name)
|
||||
assert_equal("FedEx", fed_ex.value)
|
||||
assert_equal(1, fed_ex.version)
|
||||
assert_equal("/acme", fed_ex.path)
|
||||
assert_equal(true, fed_ex.secure)
|
||||
|
||||
name = cookies.find { |c| c.name == 'name' }
|
||||
assert_not_nil(name)
|
||||
assert_equal("name", name.name)
|
||||
assert_equal("Aaron", name.value)
|
||||
assert_equal(1, name.version)
|
||||
assert_equal("/acme", name.path)
|
||||
|
||||
customer = cookies.find { |c| c.name == 'CUSTOMER' }
|
||||
assert_not_nil(customer)
|
||||
assert_equal("CUSTOMER", customer.name)
|
||||
assert_equal("WILE_E_COYOTE", customer.value)
|
||||
assert_equal(0, customer.version)
|
||||
assert_equal("/", customer.path)
|
||||
assert_equal(Time.utc(1999, 11, 9, 23, 12, 40), customer.expires)
|
||||
end
|
||||
end
|
|
@ -1,71 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "test/unit"
|
||||
require "webrick"
|
||||
require_relative "utils"
|
||||
|
||||
class TestDoNotReverseLookup < Test::Unit::TestCase
|
||||
class DNRL < WEBrick::GenericServer
|
||||
def run(sock)
|
||||
sock << sock.do_not_reverse_lookup.to_s
|
||||
end
|
||||
end
|
||||
|
||||
@@original_do_not_reverse_lookup_value = Socket.do_not_reverse_lookup
|
||||
|
||||
def teardown
|
||||
Socket.do_not_reverse_lookup = @@original_do_not_reverse_lookup_value
|
||||
end
|
||||
|
||||
def do_not_reverse_lookup?(config)
|
||||
result = nil
|
||||
TestWEBrick.start_server(DNRL, config) do |server, addr, port, log|
|
||||
TCPSocket.open(addr, port) do |sock|
|
||||
result = {'true' => true, 'false' => false}[sock.gets]
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# +--------------------------------------------------------------------------+
|
||||
# | Expected interaction between Socket.do_not_reverse_lookup |
|
||||
# | and WEBrick::Config::General[:DoNotReverseLookup] |
|
||||
# +----------------------------+---------------------------------------------+
|
||||
# | |WEBrick::Config::General[:DoNotReverseLookup]|
|
||||
# +----------------------------+--------------+---------------+--------------+
|
||||
# |Socket.do_not_reverse_lookup| TRUE | FALSE | NIL |
|
||||
# +----------------------------+--------------+---------------+--------------+
|
||||
# | TRUE | true | false | true |
|
||||
# +----------------------------+--------------+---------------+--------------+
|
||||
# | FALSE | true | false | false |
|
||||
# +----------------------------+--------------+---------------+--------------+
|
||||
|
||||
def test_socket_dnrl_true_server_dnrl_true
|
||||
Socket.do_not_reverse_lookup = true
|
||||
assert_equal(true, do_not_reverse_lookup?(:DoNotReverseLookup => true))
|
||||
end
|
||||
|
||||
def test_socket_dnrl_true_server_dnrl_false
|
||||
Socket.do_not_reverse_lookup = true
|
||||
assert_equal(false, do_not_reverse_lookup?(:DoNotReverseLookup => false))
|
||||
end
|
||||
|
||||
def test_socket_dnrl_true_server_dnrl_nil
|
||||
Socket.do_not_reverse_lookup = true
|
||||
assert_equal(true, do_not_reverse_lookup?(:DoNotReverseLookup => nil))
|
||||
end
|
||||
|
||||
def test_socket_dnrl_false_server_dnrl_true
|
||||
Socket.do_not_reverse_lookup = false
|
||||
assert_equal(true, do_not_reverse_lookup?(:DoNotReverseLookup => true))
|
||||
end
|
||||
|
||||
def test_socket_dnrl_false_server_dnrl_false
|
||||
Socket.do_not_reverse_lookup = false
|
||||
assert_equal(false, do_not_reverse_lookup?(:DoNotReverseLookup => false))
|
||||
end
|
||||
|
||||
def test_socket_dnrl_false_server_dnrl_nil
|
||||
Socket.do_not_reverse_lookup = false
|
||||
assert_equal(false, do_not_reverse_lookup?(:DoNotReverseLookup => nil))
|
||||
end
|
||||
end
|
|
@ -1,397 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "test/unit"
|
||||
require_relative "utils.rb"
|
||||
require "webrick"
|
||||
require "stringio"
|
||||
require "tmpdir"
|
||||
|
||||
class WEBrick::TestFileHandler < Test::Unit::TestCase
|
||||
def teardown
|
||||
WEBrick::Utils::TimeoutHandler.terminate
|
||||
super
|
||||
end
|
||||
|
||||
def default_file_handler(filename)
|
||||
klass = WEBrick::HTTPServlet::DefaultFileHandler
|
||||
klass.new(WEBrick::Config::HTTP, filename)
|
||||
end
|
||||
|
||||
def windows?
|
||||
File.directory?("\\")
|
||||
end
|
||||
|
||||
def get_res_body(res)
|
||||
sio = StringIO.new
|
||||
sio.binmode
|
||||
res.send_body(sio)
|
||||
sio.string
|
||||
end
|
||||
|
||||
def make_range_request(range_spec)
|
||||
msg = <<-END_OF_REQUEST
|
||||
GET / HTTP/1.0
|
||||
Range: #{range_spec}
|
||||
|
||||
END_OF_REQUEST
|
||||
return StringIO.new(msg.gsub(/^ {6}/, ""))
|
||||
end
|
||||
|
||||
def make_range_response(file, range_spec)
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(make_range_request(range_spec))
|
||||
res = WEBrick::HTTPResponse.new(WEBrick::Config::HTTP)
|
||||
size = File.size(file)
|
||||
handler = default_file_handler(file)
|
||||
handler.make_partial_content(req, res, file, size)
|
||||
return res
|
||||
end
|
||||
|
||||
def test_make_partial_content
|
||||
filename = __FILE__
|
||||
filesize = File.size(filename)
|
||||
|
||||
res = make_range_response(filename, "bytes=#{filesize-100}-")
|
||||
assert_match(%r{^text/plain}, res["content-type"])
|
||||
assert_equal(100, get_res_body(res).size)
|
||||
|
||||
res = make_range_response(filename, "bytes=-100")
|
||||
assert_match(%r{^text/plain}, res["content-type"])
|
||||
assert_equal(100, get_res_body(res).size)
|
||||
|
||||
res = make_range_response(filename, "bytes=0-99")
|
||||
assert_match(%r{^text/plain}, res["content-type"])
|
||||
assert_equal(100, get_res_body(res).size)
|
||||
|
||||
res = make_range_response(filename, "bytes=100-199")
|
||||
assert_match(%r{^text/plain}, res["content-type"])
|
||||
assert_equal(100, get_res_body(res).size)
|
||||
|
||||
res = make_range_response(filename, "bytes=0-0")
|
||||
assert_match(%r{^text/plain}, res["content-type"])
|
||||
assert_equal(1, get_res_body(res).size)
|
||||
|
||||
res = make_range_response(filename, "bytes=-1")
|
||||
assert_match(%r{^text/plain}, res["content-type"])
|
||||
assert_equal(1, get_res_body(res).size)
|
||||
|
||||
res = make_range_response(filename, "bytes=0-0, -2")
|
||||
assert_match(%r{^multipart/byteranges}, res["content-type"])
|
||||
body = get_res_body(res)
|
||||
boundary = /; boundary=(.+)/.match(res['content-type'])[1]
|
||||
off = filesize - 2
|
||||
last = filesize - 1
|
||||
|
||||
exp = "--#{boundary}\r\n" \
|
||||
"Content-Type: text/plain\r\n" \
|
||||
"Content-Range: bytes 0-0/#{filesize}\r\n" \
|
||||
"\r\n" \
|
||||
"#{File.read(__FILE__, 1)}\r\n" \
|
||||
"--#{boundary}\r\n" \
|
||||
"Content-Type: text/plain\r\n" \
|
||||
"Content-Range: bytes #{off}-#{last}/#{filesize}\r\n" \
|
||||
"\r\n" \
|
||||
"#{File.read(__FILE__, 2, off)}\r\n" \
|
||||
"--#{boundary}--\r\n"
|
||||
assert_equal exp, body
|
||||
end
|
||||
|
||||
def test_filehandler
|
||||
config = { :DocumentRoot => File.dirname(__FILE__), }
|
||||
this_file = File.basename(__FILE__)
|
||||
filesize = File.size(__FILE__)
|
||||
this_data = File.binread(__FILE__)
|
||||
range = nil
|
||||
bug2593 = '[ruby-dev:40030]'
|
||||
|
||||
TestWEBrick.start_httpserver(config) do |server, addr, port, log|
|
||||
begin
|
||||
server[:DocumentRootOptions][:NondisclosureName] = []
|
||||
http = Net::HTTP.new(addr, port)
|
||||
req = Net::HTTP::Get.new("/")
|
||||
http.request(req){|res|
|
||||
assert_equal("200", res.code, log.call)
|
||||
assert_equal("text/html", res.content_type, log.call)
|
||||
assert_match(/HREF="#{this_file}"/, res.body, log.call)
|
||||
}
|
||||
req = Net::HTTP::Get.new("/#{this_file}")
|
||||
http.request(req){|res|
|
||||
assert_equal("200", res.code, log.call)
|
||||
assert_equal("text/plain", res.content_type, log.call)
|
||||
assert_equal(this_data, res.body, log.call)
|
||||
}
|
||||
|
||||
req = Net::HTTP::Get.new("/#{this_file}", "range"=>"bytes=#{filesize-100}-")
|
||||
http.request(req){|res|
|
||||
assert_equal("206", res.code, log.call)
|
||||
assert_equal("text/plain", res.content_type, log.call)
|
||||
assert_nothing_raised(bug2593) {range = res.content_range}
|
||||
assert_equal((filesize-100)..(filesize-1), range, log.call)
|
||||
assert_equal(this_data[-100..-1], res.body, log.call)
|
||||
}
|
||||
|
||||
req = Net::HTTP::Get.new("/#{this_file}", "range"=>"bytes=-100")
|
||||
http.request(req){|res|
|
||||
assert_equal("206", res.code, log.call)
|
||||
assert_equal("text/plain", res.content_type, log.call)
|
||||
assert_nothing_raised(bug2593) {range = res.content_range}
|
||||
assert_equal((filesize-100)..(filesize-1), range, log.call)
|
||||
assert_equal(this_data[-100..-1], res.body, log.call)
|
||||
}
|
||||
|
||||
req = Net::HTTP::Get.new("/#{this_file}", "range"=>"bytes=0-99")
|
||||
http.request(req){|res|
|
||||
assert_equal("206", res.code, log.call)
|
||||
assert_equal("text/plain", res.content_type, log.call)
|
||||
assert_nothing_raised(bug2593) {range = res.content_range}
|
||||
assert_equal(0..99, range, log.call)
|
||||
assert_equal(this_data[0..99], res.body, log.call)
|
||||
}
|
||||
|
||||
req = Net::HTTP::Get.new("/#{this_file}", "range"=>"bytes=100-199")
|
||||
http.request(req){|res|
|
||||
assert_equal("206", res.code, log.call)
|
||||
assert_equal("text/plain", res.content_type, log.call)
|
||||
assert_nothing_raised(bug2593) {range = res.content_range}
|
||||
assert_equal(100..199, range, log.call)
|
||||
assert_equal(this_data[100..199], res.body, log.call)
|
||||
}
|
||||
|
||||
req = Net::HTTP::Get.new("/#{this_file}", "range"=>"bytes=0-0")
|
||||
http.request(req){|res|
|
||||
assert_equal("206", res.code, log.call)
|
||||
assert_equal("text/plain", res.content_type, log.call)
|
||||
assert_nothing_raised(bug2593) {range = res.content_range}
|
||||
assert_equal(0..0, range, log.call)
|
||||
assert_equal(this_data[0..0], res.body, log.call)
|
||||
}
|
||||
|
||||
req = Net::HTTP::Get.new("/#{this_file}", "range"=>"bytes=-1")
|
||||
http.request(req){|res|
|
||||
assert_equal("206", res.code, log.call)
|
||||
assert_equal("text/plain", res.content_type, log.call)
|
||||
assert_nothing_raised(bug2593) {range = res.content_range}
|
||||
assert_equal((filesize-1)..(filesize-1), range, log.call)
|
||||
assert_equal(this_data[-1, 1], res.body, log.call)
|
||||
}
|
||||
|
||||
req = Net::HTTP::Get.new("/#{this_file}", "range"=>"bytes=0-0, -2")
|
||||
http.request(req){|res|
|
||||
assert_equal("206", res.code, log.call)
|
||||
assert_equal("multipart/byteranges", res.content_type, log.call)
|
||||
}
|
||||
ensure
|
||||
server[:DocumentRootOptions].delete :NondisclosureName
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_non_disclosure_name
|
||||
config = { :DocumentRoot => File.dirname(__FILE__), }
|
||||
log_tester = lambda {|log, access_log|
|
||||
log = log.reject {|s| /ERROR `.*\' not found\./ =~ s }
|
||||
log = log.reject {|s| /WARN the request refers nondisclosure name/ =~ s }
|
||||
assert_equal([], log)
|
||||
}
|
||||
this_file = File.basename(__FILE__)
|
||||
TestWEBrick.start_httpserver(config, log_tester) do |server, addr, port, log|
|
||||
http = Net::HTTP.new(addr, port)
|
||||
doc_root_opts = server[:DocumentRootOptions]
|
||||
doc_root_opts[:NondisclosureName] = %w(.ht* *~ test_*)
|
||||
req = Net::HTTP::Get.new("/")
|
||||
http.request(req){|res|
|
||||
assert_equal("200", res.code, log.call)
|
||||
assert_equal("text/html", res.content_type, log.call)
|
||||
assert_no_match(/HREF="#{File.basename(__FILE__)}"/, res.body)
|
||||
}
|
||||
req = Net::HTTP::Get.new("/#{this_file}")
|
||||
http.request(req){|res|
|
||||
assert_equal("404", res.code, log.call)
|
||||
}
|
||||
doc_root_opts[:NondisclosureName] = %w(.ht* *~ TEST_*)
|
||||
http.request(req){|res|
|
||||
assert_equal("404", res.code, log.call)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def test_directory_traversal
|
||||
return if File.executable?(__FILE__) # skip on strange file system
|
||||
|
||||
config = { :DocumentRoot => File.dirname(__FILE__), }
|
||||
log_tester = lambda {|log, access_log|
|
||||
log = log.reject {|s| /ERROR bad URI/ =~ s }
|
||||
log = log.reject {|s| /ERROR `.*\' not found\./ =~ s }
|
||||
assert_equal([], log)
|
||||
}
|
||||
TestWEBrick.start_httpserver(config, log_tester) do |server, addr, port, log|
|
||||
http = Net::HTTP.new(addr, port)
|
||||
req = Net::HTTP::Get.new("/../../")
|
||||
http.request(req){|res| assert_equal("400", res.code, log.call) }
|
||||
req = Net::HTTP::Get.new("/..%5c../#{File.basename(__FILE__)}")
|
||||
http.request(req){|res| assert_equal(windows? ? "200" : "404", res.code, log.call) }
|
||||
req = Net::HTTP::Get.new("/..%5c..%5cruby.c")
|
||||
http.request(req){|res| assert_equal("404", res.code, log.call) }
|
||||
end
|
||||
end
|
||||
|
||||
def test_unwise_in_path
|
||||
if windows?
|
||||
config = { :DocumentRoot => File.dirname(__FILE__), }
|
||||
TestWEBrick.start_httpserver(config) do |server, addr, port, log|
|
||||
http = Net::HTTP.new(addr, port)
|
||||
req = Net::HTTP::Get.new("/..%5c..")
|
||||
http.request(req){|res| assert_equal("301", res.code, log.call) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_short_filename
|
||||
return if File.executable?(__FILE__) # skip on strange file system
|
||||
|
||||
log_tester = lambda {|log, access_log|
|
||||
log = log.reject {|s| /ERROR `.*\' not found\./ =~ s }
|
||||
log = log.reject {|s| /WARN the request refers nondisclosure name/ =~ s }
|
||||
assert_equal([], log)
|
||||
}
|
||||
TestWEBrick.start_cgi_server({}, log_tester) do |server, addr, port, log|
|
||||
http = Net::HTTP.new(addr, port)
|
||||
if windows?
|
||||
root = File.dirname(__FILE__).tr("/", "\\")
|
||||
fname = IO.popen(%W[dir /x #{root}\\webrick_long_filename.cgi], encoding: "binary", &:read)
|
||||
fname.sub!(/\A.*$^$.*$^$/m, '')
|
||||
if fname
|
||||
fname = fname[/\s(w.+?cgi)\s/i, 1]
|
||||
fname.downcase!
|
||||
end
|
||||
else
|
||||
fname = "webric~1.cgi"
|
||||
end
|
||||
req = Net::HTTP::Get.new("/#{fname}/test")
|
||||
http.request(req) do |res|
|
||||
if windows?
|
||||
assert_equal("200", res.code, log.call)
|
||||
assert_equal("/test", res.body, log.call)
|
||||
else
|
||||
assert_equal("404", res.code, log.call)
|
||||
end
|
||||
end
|
||||
|
||||
req = Net::HTTP::Get.new("/.htaccess")
|
||||
http.request(req) {|res| assert_equal("404", res.code, log.call) }
|
||||
req = Net::HTTP::Get.new("/htacce~1")
|
||||
http.request(req) {|res| assert_equal("404", res.code, log.call) }
|
||||
req = Net::HTTP::Get.new("/HTACCE~1")
|
||||
http.request(req) {|res| assert_equal("404", res.code, log.call) }
|
||||
end
|
||||
end
|
||||
|
||||
def test_multibyte_char_in_path
|
||||
if Encoding.default_external == Encoding.find('US-ASCII')
|
||||
reset_encoding = true
|
||||
verb = $VERBOSE
|
||||
$VERBOSE = false
|
||||
Encoding.default_external = Encoding.find('UTF-8')
|
||||
end
|
||||
|
||||
c = "\u00a7"
|
||||
begin
|
||||
c = c.encode('filesystem')
|
||||
rescue EncodingError
|
||||
c = c.b
|
||||
end
|
||||
Dir.mktmpdir(c) do |dir|
|
||||
basename = "#{c}.txt"
|
||||
File.write("#{dir}/#{basename}", "test_multibyte_char_in_path")
|
||||
Dir.mkdir("#{dir}/#{c}")
|
||||
File.write("#{dir}/#{c}/#{basename}", "nested")
|
||||
config = {
|
||||
:DocumentRoot => dir,
|
||||
:DirectoryIndex => [basename],
|
||||
}
|
||||
TestWEBrick.start_httpserver(config) do |server, addr, port, log|
|
||||
http = Net::HTTP.new(addr, port)
|
||||
path = "/#{basename}"
|
||||
req = Net::HTTP::Get.new(WEBrick::HTTPUtils::escape(path))
|
||||
http.request(req){|res| assert_equal("200", res.code, log.call + "\nFilesystem encoding is #{Encoding.find('filesystem')}") }
|
||||
path = "/#{c}/#{basename}"
|
||||
req = Net::HTTP::Get.new(WEBrick::HTTPUtils::escape(path))
|
||||
http.request(req){|res| assert_equal("200", res.code, log.call) }
|
||||
req = Net::HTTP::Get.new('/')
|
||||
http.request(req){|res|
|
||||
assert_equal("test_multibyte_char_in_path", res.body, log.call)
|
||||
}
|
||||
end
|
||||
end
|
||||
ensure
|
||||
if reset_encoding
|
||||
Encoding.default_external = Encoding.find('US-ASCII')
|
||||
$VERBOSE = verb
|
||||
end
|
||||
end
|
||||
|
||||
def test_script_disclosure
|
||||
return if File.executable?(__FILE__) # skip on strange file system
|
||||
|
||||
config = {
|
||||
:CGIInterpreter => TestWEBrick::RubyBinArray,
|
||||
:DocumentRoot => File.dirname(__FILE__),
|
||||
:CGIPathEnv => ENV['PATH'],
|
||||
:RequestCallback => Proc.new{|req, res|
|
||||
def req.meta_vars
|
||||
meta = super
|
||||
meta["RUBYLIB"] = $:.join(File::PATH_SEPARATOR)
|
||||
meta[RbConfig::CONFIG['LIBPATHENV']] = ENV[RbConfig::CONFIG['LIBPATHENV']] if RbConfig::CONFIG['LIBPATHENV']
|
||||
return meta
|
||||
end
|
||||
},
|
||||
}
|
||||
log_tester = lambda {|log, access_log|
|
||||
log = log.reject {|s| /ERROR `.*\' not found\./ =~ s }
|
||||
assert_equal([], log)
|
||||
}
|
||||
TestWEBrick.start_httpserver(config, log_tester) do |server, addr, port, log|
|
||||
http = Net::HTTP.new(addr, port)
|
||||
http.read_timeout = EnvUtil.apply_timeout_scale(60)
|
||||
http.write_timeout = EnvUtil.apply_timeout_scale(60) if http.respond_to?(:write_timeout=)
|
||||
|
||||
req = Net::HTTP::Get.new("/webrick.cgi/test")
|
||||
http.request(req) do |res|
|
||||
assert_equal("200", res.code, log.call)
|
||||
assert_equal("/test", res.body, log.call)
|
||||
end
|
||||
|
||||
resok = windows?
|
||||
response_assertion = Proc.new do |res|
|
||||
if resok
|
||||
assert_equal("200", res.code, log.call)
|
||||
assert_equal("/test", res.body, log.call)
|
||||
else
|
||||
assert_equal("404", res.code, log.call)
|
||||
end
|
||||
end
|
||||
req = Net::HTTP::Get.new("/webrick.cgi%20/test")
|
||||
http.request(req, &response_assertion)
|
||||
req = Net::HTTP::Get.new("/webrick.cgi./test")
|
||||
http.request(req, &response_assertion)
|
||||
resok &&= File.exist?(__FILE__+"::$DATA")
|
||||
req = Net::HTTP::Get.new("/webrick.cgi::$DATA/test")
|
||||
http.request(req, &response_assertion)
|
||||
end
|
||||
end
|
||||
|
||||
def test_erbhandler
|
||||
config = { :DocumentRoot => File.dirname(__FILE__) }
|
||||
log_tester = lambda {|log, access_log|
|
||||
log = log.reject {|s| /ERROR `.*\' not found\./ =~ s }
|
||||
assert_equal([], log)
|
||||
}
|
||||
TestWEBrick.start_httpserver(config, log_tester) do |server, addr, port, log|
|
||||
http = Net::HTTP.new(addr, port)
|
||||
req = Net::HTTP::Get.new("/webrick.rhtml")
|
||||
http.request(req) do |res|
|
||||
assert_equal("200", res.code, log.call)
|
||||
assert_match %r!\Areq to http://[^/]+/webrick\.rhtml {}\n!, res.body
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
require "tempfile"
|
||||
require "test/unit"
|
||||
require "webrick/httpauth/htgroup"
|
||||
|
||||
class TestHtgroup < Test::Unit::TestCase
|
||||
def test_htgroup
|
||||
Tempfile.create('test_htgroup') do |tmpfile|
|
||||
tmpfile.close
|
||||
tmp_group = WEBrick::HTTPAuth::Htgroup.new(tmpfile.path)
|
||||
tmp_group.add 'superheroes', %w[spiderman batman]
|
||||
tmp_group.add 'supervillains', %w[joker]
|
||||
tmp_group.flush
|
||||
|
||||
htgroup = WEBrick::HTTPAuth::Htgroup.new(tmpfile.path)
|
||||
assert_equal(htgroup.members('superheroes'), %w[spiderman batman])
|
||||
assert_equal(htgroup.members('supervillains'), %w[joker])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "test/unit"
|
||||
require "webrick/htmlutils"
|
||||
|
||||
class TestWEBrickHTMLUtils < Test::Unit::TestCase
|
||||
include WEBrick::HTMLUtils
|
||||
|
||||
def test_escape
|
||||
assert_equal("foo", escape("foo"))
|
||||
assert_equal("foo bar", escape("foo bar"))
|
||||
assert_equal("foo&bar", escape("foo&bar"))
|
||||
assert_equal("foo"bar", escape("foo\"bar"))
|
||||
assert_equal("foo>bar", escape("foo>bar"))
|
||||
assert_equal("foo<bar", escape("foo<bar"))
|
||||
assert_equal("\u{3053 3093 306B 3061 306F}", escape("\u{3053 3093 306B 3061 306F}"))
|
||||
bug8425 = '[Bug #8425] [ruby-core:55052]'
|
||||
assert_nothing_raised(ArgumentError, Encoding::CompatibilityError, bug8425) {
|
||||
assert_equal("\u{3053 3093 306B}\xff<", escape("\u{3053 3093 306B}\xff<"))
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,366 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "test/unit"
|
||||
require "net/http"
|
||||
require "tempfile"
|
||||
require "webrick"
|
||||
require "webrick/httpauth/basicauth"
|
||||
require "stringio"
|
||||
require_relative "utils"
|
||||
|
||||
class TestWEBrickHTTPAuth < Test::Unit::TestCase
|
||||
def teardown
|
||||
WEBrick::Utils::TimeoutHandler.terminate
|
||||
super
|
||||
end
|
||||
|
||||
def test_basic_auth
|
||||
log_tester = lambda {|log, access_log|
|
||||
assert_equal(1, log.length)
|
||||
assert_match(/ERROR WEBrick::HTTPStatus::Unauthorized/, log[0])
|
||||
}
|
||||
TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
|
||||
realm = "WEBrick's realm"
|
||||
path = "/basic_auth"
|
||||
|
||||
server.mount_proc(path){|req, res|
|
||||
WEBrick::HTTPAuth.basic_auth(req, res, realm){|user, pass|
|
||||
user == "webrick" && pass == "supersecretpassword"
|
||||
}
|
||||
res.body = "hoge"
|
||||
}
|
||||
http = Net::HTTP.new(addr, port)
|
||||
g = Net::HTTP::Get.new(path)
|
||||
g.basic_auth("webrick", "supersecretpassword")
|
||||
http.request(g){|res| assert_equal("hoge", res.body, log.call)}
|
||||
g.basic_auth("webrick", "not super")
|
||||
http.request(g){|res| assert_not_equal("hoge", res.body, log.call)}
|
||||
}
|
||||
end
|
||||
|
||||
def test_basic_auth_sha
|
||||
Tempfile.create("test_webrick_auth") {|tmpfile|
|
||||
tmpfile.puts("webrick:{SHA}GJYFRpBbdchp595jlh3Bhfmgp8k=")
|
||||
tmpfile.flush
|
||||
assert_raise(NotImplementedError){
|
||||
WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def test_basic_auth_md5
|
||||
Tempfile.create("test_webrick_auth") {|tmpfile|
|
||||
tmpfile.puts("webrick:$apr1$IOVMD/..$rmnOSPXr0.wwrLPZHBQZy0")
|
||||
tmpfile.flush
|
||||
assert_raise(NotImplementedError){
|
||||
WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
[nil, :crypt, :bcrypt].each do |hash_algo|
|
||||
# OpenBSD does not support insecure DES-crypt
|
||||
next if /openbsd/ =~ RUBY_PLATFORM && hash_algo != :bcrypt
|
||||
|
||||
begin
|
||||
case hash_algo
|
||||
when :crypt
|
||||
# require 'string/crypt'
|
||||
when :bcrypt
|
||||
require 'bcrypt'
|
||||
end
|
||||
rescue LoadError
|
||||
next
|
||||
end
|
||||
|
||||
define_method(:"test_basic_auth_htpasswd_#{hash_algo}") do
|
||||
log_tester = lambda {|log, access_log|
|
||||
log.reject! {|line| /\A\s*\z/ =~ line }
|
||||
pats = [
|
||||
/ERROR Basic WEBrick's realm: webrick: password unmatch\./,
|
||||
/ERROR WEBrick::HTTPStatus::Unauthorized/
|
||||
]
|
||||
pats.each {|pat|
|
||||
assert(!log.grep(pat).empty?, "webrick log doesn't have expected error: #{pat.inspect}")
|
||||
log.reject! {|line| pat =~ line }
|
||||
}
|
||||
assert_equal([], log)
|
||||
}
|
||||
TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
|
||||
realm = "WEBrick's realm"
|
||||
path = "/basic_auth2"
|
||||
|
||||
Tempfile.create("test_webrick_auth") {|tmpfile|
|
||||
tmpfile.close
|
||||
tmp_pass = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path, password_hash: hash_algo)
|
||||
tmp_pass.set_passwd(realm, "webrick", "supersecretpassword")
|
||||
tmp_pass.set_passwd(realm, "foo", "supersecretpassword")
|
||||
tmp_pass.flush
|
||||
|
||||
htpasswd = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path, password_hash: hash_algo)
|
||||
users = []
|
||||
htpasswd.each{|user, pass| users << user }
|
||||
assert_equal(2, users.size, log.call)
|
||||
assert(users.member?("webrick"), log.call)
|
||||
assert(users.member?("foo"), log.call)
|
||||
|
||||
server.mount_proc(path){|req, res|
|
||||
auth = WEBrick::HTTPAuth::BasicAuth.new(
|
||||
:Realm => realm, :UserDB => htpasswd,
|
||||
:Logger => server.logger
|
||||
)
|
||||
auth.authenticate(req, res)
|
||||
res.body = "hoge"
|
||||
}
|
||||
http = Net::HTTP.new(addr, port)
|
||||
g = Net::HTTP::Get.new(path)
|
||||
g.basic_auth("webrick", "supersecretpassword")
|
||||
http.request(g){|res| assert_equal("hoge", res.body, log.call)}
|
||||
g.basic_auth("webrick", "not super")
|
||||
http.request(g){|res| assert_not_equal("hoge", res.body, log.call)}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
define_method(:"test_basic_auth_bad_username_htpasswd_#{hash_algo}") do
|
||||
log_tester = lambda {|log, access_log|
|
||||
assert_equal(2, log.length)
|
||||
assert_match(/ERROR Basic WEBrick's realm: foo\\ebar: the user is not allowed\./, log[0])
|
||||
assert_match(/ERROR WEBrick::HTTPStatus::Unauthorized/, log[1])
|
||||
}
|
||||
TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
|
||||
realm = "WEBrick's realm"
|
||||
path = "/basic_auth"
|
||||
|
||||
Tempfile.create("test_webrick_auth") {|tmpfile|
|
||||
tmpfile.close
|
||||
tmp_pass = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path, password_hash: hash_algo)
|
||||
tmp_pass.set_passwd(realm, "webrick", "supersecretpassword")
|
||||
tmp_pass.set_passwd(realm, "foo", "supersecretpassword")
|
||||
tmp_pass.flush
|
||||
|
||||
htpasswd = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path, password_hash: hash_algo)
|
||||
users = []
|
||||
htpasswd.each{|user, pass| users << user }
|
||||
server.mount_proc(path){|req, res|
|
||||
auth = WEBrick::HTTPAuth::BasicAuth.new(
|
||||
:Realm => realm, :UserDB => htpasswd,
|
||||
:Logger => server.logger
|
||||
)
|
||||
auth.authenticate(req, res)
|
||||
res.body = "hoge"
|
||||
}
|
||||
http = Net::HTTP.new(addr, port)
|
||||
g = Net::HTTP::Get.new(path)
|
||||
g.basic_auth("foo\ebar", "passwd")
|
||||
http.request(g){|res| assert_not_equal("hoge", res.body, log.call) }
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
DIGESTRES_ = /
|
||||
([a-zA-Z\-]+)
|
||||
[ \t]*(?:\r\n[ \t]*)*
|
||||
=
|
||||
[ \t]*(?:\r\n[ \t]*)*
|
||||
(?:
|
||||
"((?:[^"]+|\\[\x00-\x7F])*)" |
|
||||
([!\#$%&'*+\-.0-9A-Z^_`a-z|~]+)
|
||||
)/x
|
||||
|
||||
def test_digest_auth
|
||||
log_tester = lambda {|log, access_log|
|
||||
log.reject! {|line| /\A\s*\z/ =~ line }
|
||||
pats = [
|
||||
/ERROR Digest WEBrick's realm: no credentials in the request\./,
|
||||
/ERROR WEBrick::HTTPStatus::Unauthorized/,
|
||||
/ERROR Digest WEBrick's realm: webrick: digest unmatch\./
|
||||
]
|
||||
pats.each {|pat|
|
||||
assert(!log.grep(pat).empty?, "webrick log doesn't have expected error: #{pat.inspect}")
|
||||
log.reject! {|line| pat =~ line }
|
||||
}
|
||||
assert_equal([], log)
|
||||
}
|
||||
TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
|
||||
realm = "WEBrick's realm"
|
||||
path = "/digest_auth"
|
||||
|
||||
Tempfile.create("test_webrick_auth") {|tmpfile|
|
||||
tmpfile.close
|
||||
tmp_pass = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path)
|
||||
tmp_pass.set_passwd(realm, "webrick", "supersecretpassword")
|
||||
tmp_pass.set_passwd(realm, "foo", "supersecretpassword")
|
||||
tmp_pass.flush
|
||||
|
||||
htdigest = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path)
|
||||
users = []
|
||||
htdigest.each{|user, pass| users << user }
|
||||
assert_equal(2, users.size, log.call)
|
||||
assert(users.member?("webrick"), log.call)
|
||||
assert(users.member?("foo"), log.call)
|
||||
|
||||
auth = WEBrick::HTTPAuth::DigestAuth.new(
|
||||
:Realm => realm, :UserDB => htdigest,
|
||||
:Algorithm => 'MD5',
|
||||
:Logger => server.logger
|
||||
)
|
||||
server.mount_proc(path){|req, res|
|
||||
auth.authenticate(req, res)
|
||||
res.body = "hoge"
|
||||
}
|
||||
|
||||
Net::HTTP.start(addr, port) do |http|
|
||||
g = Net::HTTP::Get.new(path)
|
||||
params = {}
|
||||
http.request(g) do |res|
|
||||
assert_equal('401', res.code, log.call)
|
||||
res["www-authenticate"].scan(DIGESTRES_) do |key, quoted, token|
|
||||
params[key.downcase] = token || quoted.delete('\\')
|
||||
end
|
||||
params['uri'] = "http://#{addr}:#{port}#{path}"
|
||||
end
|
||||
|
||||
g['Authorization'] = credentials_for_request('webrick', "supersecretpassword", params)
|
||||
http.request(g){|res| assert_equal("hoge", res.body, log.call)}
|
||||
|
||||
params['algorithm'].downcase! #4936
|
||||
g['Authorization'] = credentials_for_request('webrick', "supersecretpassword", params)
|
||||
http.request(g){|res| assert_equal("hoge", res.body, log.call)}
|
||||
|
||||
g['Authorization'] = credentials_for_request('webrick', "not super", params)
|
||||
http.request(g){|res| assert_not_equal("hoge", res.body, log.call)}
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def test_digest_auth_int
|
||||
log_tester = lambda {|log, access_log|
|
||||
log.reject! {|line| /\A\s*\z/ =~ line }
|
||||
pats = [
|
||||
/ERROR Digest wb auth-int realm: no credentials in the request\./,
|
||||
/ERROR WEBrick::HTTPStatus::Unauthorized/,
|
||||
/ERROR Digest wb auth-int realm: foo: digest unmatch\./
|
||||
]
|
||||
pats.each {|pat|
|
||||
assert(!log.grep(pat).empty?, "webrick log doesn't have expected error: #{pat.inspect}")
|
||||
log.reject! {|line| pat =~ line }
|
||||
}
|
||||
assert_equal([], log)
|
||||
}
|
||||
TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
|
||||
realm = "wb auth-int realm"
|
||||
path = "/digest_auth_int"
|
||||
|
||||
Tempfile.create("test_webrick_auth_int") {|tmpfile|
|
||||
tmpfile.close
|
||||
tmp_pass = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path)
|
||||
tmp_pass.set_passwd(realm, "foo", "Hunter2")
|
||||
tmp_pass.flush
|
||||
|
||||
htdigest = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path)
|
||||
users = []
|
||||
htdigest.each{|user, pass| users << user }
|
||||
assert_equal %w(foo), users
|
||||
|
||||
auth = WEBrick::HTTPAuth::DigestAuth.new(
|
||||
:Realm => realm, :UserDB => htdigest,
|
||||
:Algorithm => 'MD5',
|
||||
:Logger => server.logger,
|
||||
:Qop => %w(auth-int),
|
||||
)
|
||||
server.mount_proc(path){|req, res|
|
||||
auth.authenticate(req, res)
|
||||
res.body = "bbb"
|
||||
}
|
||||
Net::HTTP.start(addr, port) do |http|
|
||||
post = Net::HTTP::Post.new(path)
|
||||
params = {}
|
||||
data = 'hello=world'
|
||||
body = StringIO.new(data)
|
||||
post.content_length = data.bytesize
|
||||
post['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
post.body_stream = body
|
||||
|
||||
http.request(post) do |res|
|
||||
assert_equal('401', res.code, log.call)
|
||||
res["www-authenticate"].scan(DIGESTRES_) do |key, quoted, token|
|
||||
params[key.downcase] = token || quoted.delete('\\')
|
||||
end
|
||||
params['uri'] = "http://#{addr}:#{port}#{path}"
|
||||
end
|
||||
|
||||
body.rewind
|
||||
cred = credentials_for_request('foo', 'Hunter3', params, body)
|
||||
post['Authorization'] = cred
|
||||
post.body_stream = body
|
||||
http.request(post){|res|
|
||||
assert_equal('401', res.code, log.call)
|
||||
assert_not_equal("bbb", res.body, log.call)
|
||||
}
|
||||
|
||||
body.rewind
|
||||
cred = credentials_for_request('foo', 'Hunter2', params, body)
|
||||
post['Authorization'] = cred
|
||||
post.body_stream = body
|
||||
http.request(post){|res| assert_equal("bbb", res.body, log.call)}
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def test_digest_auth_invalid
|
||||
digest_auth = WEBrick::HTTPAuth::DigestAuth.new(Realm: 'realm', UserDB: '')
|
||||
|
||||
def digest_auth.error(fmt, *)
|
||||
end
|
||||
|
||||
def digest_auth.try_bad_request(len)
|
||||
request = {"Authorization" => %[Digest a="#{'\b'*len}]}
|
||||
authenticate request, nil
|
||||
end
|
||||
|
||||
bad_request = WEBrick::HTTPStatus::BadRequest
|
||||
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
assert_raise(bad_request) {digest_auth.try_bad_request(10)}
|
||||
limit = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0)
|
||||
[20, 50, 100, 200].each do |len|
|
||||
assert_raise(bad_request) do
|
||||
Timeout.timeout(len*limit) {digest_auth.try_bad_request(len)}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def credentials_for_request(user, password, params, body = nil)
|
||||
cnonce = "hoge"
|
||||
nonce_count = 1
|
||||
ha1 = "#{user}:#{params['realm']}:#{password}"
|
||||
if body
|
||||
dig = Digest::MD5.new
|
||||
while buf = body.read(16384)
|
||||
dig.update(buf)
|
||||
end
|
||||
body.rewind
|
||||
ha2 = "POST:#{params['uri']}:#{dig.hexdigest}"
|
||||
else
|
||||
ha2 = "GET:#{params['uri']}"
|
||||
end
|
||||
|
||||
request_digest =
|
||||
"#{Digest::MD5.hexdigest(ha1)}:" \
|
||||
"#{params['nonce']}:#{'%08x' % nonce_count}:#{cnonce}:#{params['qop']}:" \
|
||||
"#{Digest::MD5.hexdigest(ha2)}"
|
||||
"Digest username=\"#{user}\"" \
|
||||
", realm=\"#{params['realm']}\"" \
|
||||
", nonce=\"#{params['nonce']}\"" \
|
||||
", uri=\"#{params['uri']}\"" \
|
||||
", qop=#{params['qop']}" \
|
||||
", nc=#{'%08x' % nonce_count}" \
|
||||
", cnonce=\"#{cnonce}\"" \
|
||||
", response=\"#{Digest::MD5.hexdigest(request_digest)}\"" \
|
||||
", opaque=\"#{params['opaque']}\"" \
|
||||
", algorithm=#{params['algorithm']}"
|
||||
end
|
||||
end
|
|
@ -1,467 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "test/unit"
|
||||
require "net/http"
|
||||
require "webrick"
|
||||
require "webrick/httpproxy"
|
||||
begin
|
||||
require "webrick/ssl"
|
||||
require "net/https"
|
||||
rescue LoadError
|
||||
# test_connect will be skipped
|
||||
end
|
||||
require File.expand_path("utils.rb", File.dirname(__FILE__))
|
||||
|
||||
class TestWEBrickHTTPProxy < Test::Unit::TestCase
|
||||
def teardown
|
||||
WEBrick::Utils::TimeoutHandler.terminate
|
||||
super
|
||||
end
|
||||
|
||||
def test_fake_proxy
|
||||
assert_nil(WEBrick::FakeProxyURI.scheme)
|
||||
assert_nil(WEBrick::FakeProxyURI.host)
|
||||
assert_nil(WEBrick::FakeProxyURI.port)
|
||||
assert_nil(WEBrick::FakeProxyURI.path)
|
||||
assert_nil(WEBrick::FakeProxyURI.userinfo)
|
||||
assert_raise(NoMethodError){ WEBrick::FakeProxyURI.foo }
|
||||
end
|
||||
|
||||
def test_proxy
|
||||
# Testing GET or POST to the proxy server
|
||||
# Note that the proxy server works as the origin server.
|
||||
# +------+
|
||||
# V |
|
||||
# client -------> proxy ---+
|
||||
# GET / POST GET / POST
|
||||
#
|
||||
proxy_handler_called = request_handler_called = 0
|
||||
config = {
|
||||
:ServerName => "localhost.localdomain",
|
||||
:ProxyContentHandler => Proc.new{|req, res| proxy_handler_called += 1 },
|
||||
:RequestCallback => Proc.new{|req, res| request_handler_called += 1 }
|
||||
}
|
||||
TestWEBrick.start_httpproxy(config){|server, addr, port, log|
|
||||
server.mount_proc("/"){|req, res|
|
||||
res.body = "#{req.request_method} #{req.path} #{req.body}"
|
||||
}
|
||||
http = Net::HTTP.new(addr, port, addr, port)
|
||||
|
||||
req = Net::HTTP::Get.new("/")
|
||||
http.request(req){|res|
|
||||
assert_equal("1.1 localhost.localdomain:#{port}", res["via"], log.call)
|
||||
assert_equal("GET / ", res.body, log.call)
|
||||
}
|
||||
assert_equal(1, proxy_handler_called, log.call)
|
||||
assert_equal(2, request_handler_called, log.call)
|
||||
|
||||
req = Net::HTTP::Head.new("/")
|
||||
http.request(req){|res|
|
||||
assert_equal("1.1 localhost.localdomain:#{port}", res["via"], log.call)
|
||||
assert_nil(res.body, log.call)
|
||||
}
|
||||
assert_equal(2, proxy_handler_called, log.call)
|
||||
assert_equal(4, request_handler_called, log.call)
|
||||
|
||||
req = Net::HTTP::Post.new("/")
|
||||
req.body = "post-data"
|
||||
req.content_type = "application/x-www-form-urlencoded"
|
||||
http.request(req){|res|
|
||||
assert_equal("1.1 localhost.localdomain:#{port}", res["via"], log.call)
|
||||
assert_equal("POST / post-data", res.body, log.call)
|
||||
}
|
||||
assert_equal(3, proxy_handler_called, log.call)
|
||||
assert_equal(6, request_handler_called, log.call)
|
||||
}
|
||||
end
|
||||
|
||||
def test_no_proxy
|
||||
# Testing GET or POST to the proxy server without proxy request.
|
||||
#
|
||||
# client -------> proxy
|
||||
# GET / POST
|
||||
#
|
||||
proxy_handler_called = request_handler_called = 0
|
||||
config = {
|
||||
:ServerName => "localhost.localdomain",
|
||||
:ProxyContentHandler => Proc.new{|req, res| proxy_handler_called += 1 },
|
||||
:RequestCallback => Proc.new{|req, res| request_handler_called += 1 }
|
||||
}
|
||||
TestWEBrick.start_httpproxy(config){|server, addr, port, log|
|
||||
server.mount_proc("/"){|req, res|
|
||||
res.body = "#{req.request_method} #{req.path} #{req.body}"
|
||||
}
|
||||
http = Net::HTTP.new(addr, port)
|
||||
|
||||
req = Net::HTTP::Get.new("/")
|
||||
http.request(req){|res|
|
||||
assert_nil(res["via"], log.call)
|
||||
assert_equal("GET / ", res.body, log.call)
|
||||
}
|
||||
assert_equal(0, proxy_handler_called, log.call)
|
||||
assert_equal(1, request_handler_called, log.call)
|
||||
|
||||
req = Net::HTTP::Head.new("/")
|
||||
http.request(req){|res|
|
||||
assert_nil(res["via"], log.call)
|
||||
assert_nil(res.body, log.call)
|
||||
}
|
||||
assert_equal(0, proxy_handler_called, log.call)
|
||||
assert_equal(2, request_handler_called, log.call)
|
||||
|
||||
req = Net::HTTP::Post.new("/")
|
||||
req.content_type = "application/x-www-form-urlencoded"
|
||||
req.body = "post-data"
|
||||
http.request(req){|res|
|
||||
assert_nil(res["via"], log.call)
|
||||
assert_equal("POST / post-data", res.body, log.call)
|
||||
}
|
||||
assert_equal(0, proxy_handler_called, log.call)
|
||||
assert_equal(3, request_handler_called, log.call)
|
||||
}
|
||||
end
|
||||
|
||||
def test_big_bodies
|
||||
require 'digest/md5'
|
||||
rand_str = File.read(__FILE__)
|
||||
rand_str.freeze
|
||||
nr = 1024 ** 2 / rand_str.size # bigger works, too
|
||||
exp = Digest::MD5.new
|
||||
nr.times { exp.update(rand_str) }
|
||||
exp = exp.hexdigest
|
||||
TestWEBrick.start_httpserver do |o_server, o_addr, o_port, o_log|
|
||||
o_server.mount_proc('/') do |req, res|
|
||||
case req.request_method
|
||||
when 'GET'
|
||||
res['content-type'] = 'application/octet-stream'
|
||||
if req.path == '/length'
|
||||
res['content-length'] = (nr * rand_str.size).to_s
|
||||
else
|
||||
res.chunked = true
|
||||
end
|
||||
res.body = ->(socket) { nr.times { socket.write(rand_str) } }
|
||||
when 'POST'
|
||||
dig = Digest::MD5.new
|
||||
req.body { |buf| dig.update(buf); buf.clear }
|
||||
res['content-type'] = 'text/plain'
|
||||
res['content-length'] = '32'
|
||||
res.body = dig.hexdigest
|
||||
end
|
||||
end
|
||||
|
||||
http = Net::HTTP.new(o_addr, o_port)
|
||||
IO.pipe do |rd, wr|
|
||||
headers = {
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Transfer-Encoding' => 'chunked',
|
||||
}
|
||||
post = Net::HTTP::Post.new('/', headers)
|
||||
th = Thread.new { nr.times { wr.write(rand_str) }; wr.close }
|
||||
post.body_stream = rd
|
||||
http.request(post) do |res|
|
||||
assert_equal 'text/plain', res['content-type']
|
||||
assert_equal 32, res.content_length
|
||||
assert_equal exp, res.body
|
||||
end
|
||||
assert_nil th.value
|
||||
end
|
||||
|
||||
TestWEBrick.start_httpproxy do |p_server, p_addr, p_port, p_log|
|
||||
http = Net::HTTP.new(o_addr, o_port, p_addr, p_port)
|
||||
http.request_get('/length') do |res|
|
||||
assert_equal(nr * rand_str.size, res.content_length)
|
||||
dig = Digest::MD5.new
|
||||
res.read_body { |buf| dig.update(buf); buf.clear }
|
||||
assert_equal exp, dig.hexdigest
|
||||
end
|
||||
http.request_get('/') do |res|
|
||||
assert_predicate res, :chunked?
|
||||
dig = Digest::MD5.new
|
||||
res.read_body { |buf| dig.update(buf); buf.clear }
|
||||
assert_equal exp, dig.hexdigest
|
||||
end
|
||||
|
||||
IO.pipe do |rd, wr|
|
||||
headers = {
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Content-Length' => (nr * rand_str.size).to_s,
|
||||
}
|
||||
post = Net::HTTP::Post.new('/', headers)
|
||||
th = Thread.new { nr.times { wr.write(rand_str) }; wr.close }
|
||||
post.body_stream = rd
|
||||
http.request(post) do |res|
|
||||
assert_equal 'text/plain', res['content-type']
|
||||
assert_equal 32, res.content_length
|
||||
assert_equal exp, res.body
|
||||
end
|
||||
assert_nil th.value
|
||||
end
|
||||
|
||||
IO.pipe do |rd, wr|
|
||||
headers = {
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Transfer-Encoding' => 'chunked',
|
||||
}
|
||||
post = Net::HTTP::Post.new('/', headers)
|
||||
th = Thread.new { nr.times { wr.write(rand_str) }; wr.close }
|
||||
post.body_stream = rd
|
||||
http.request(post) do |res|
|
||||
assert_equal 'text/plain', res['content-type']
|
||||
assert_equal 32, res.content_length
|
||||
assert_equal exp, res.body
|
||||
end
|
||||
assert_nil th.value
|
||||
end
|
||||
end
|
||||
end
|
||||
end if RUBY_VERSION >= '2.5'
|
||||
|
||||
def test_http10_proxy_chunked
|
||||
# Testing HTTP/1.0 client request and HTTP/1.1 chunked response
|
||||
# from origin server.
|
||||
# +------+
|
||||
# V |
|
||||
# client -------> proxy ---+
|
||||
# GET GET
|
||||
# HTTP/1.0 HTTP/1.1
|
||||
# non-chunked chunked
|
||||
#
|
||||
proxy_handler_called = request_handler_called = 0
|
||||
config = {
|
||||
:ServerName => "localhost.localdomain",
|
||||
:ProxyContentHandler => Proc.new{|req, res| proxy_handler_called += 1 },
|
||||
:RequestCallback => Proc.new{|req, res| request_handler_called += 1 }
|
||||
}
|
||||
log_tester = lambda {|log, access_log|
|
||||
log.reject! {|str|
|
||||
%r{WARN chunked is set for an HTTP/1\.0 request\. \(ignored\)} =~ str
|
||||
}
|
||||
assert_equal([], log)
|
||||
}
|
||||
TestWEBrick.start_httpproxy(config, log_tester){|server, addr, port, log|
|
||||
body = nil
|
||||
server.mount_proc("/"){|req, res|
|
||||
body = "#{req.request_method} #{req.path} #{req.body}"
|
||||
res.chunked = true
|
||||
res.body = -> (socket) { body.each_char {|c| socket.write c } }
|
||||
}
|
||||
|
||||
# Don't use Net::HTTP because it uses HTTP/1.1.
|
||||
TCPSocket.open(addr, port) {|s|
|
||||
s.write "GET / HTTP/1.0\r\nHost: localhost.localdomain\r\n\r\n"
|
||||
response = s.read
|
||||
assert_equal(body, response[/.*\z/])
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def make_certificate(key, cn)
|
||||
subject = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=#{cn}")
|
||||
exts = [
|
||||
["keyUsage", "keyEncipherment,digitalSignature", true],
|
||||
]
|
||||
cert = OpenSSL::X509::Certificate.new
|
||||
cert.version = 2
|
||||
cert.serial = 1
|
||||
cert.subject = subject
|
||||
cert.issuer = subject
|
||||
cert.public_key = key
|
||||
cert.not_before = Time.now - 3600
|
||||
cert.not_after = Time.now + 3600
|
||||
ef = OpenSSL::X509::ExtensionFactory.new(cert, cert)
|
||||
exts.each {|args| cert.add_extension(ef.create_extension(*args)) }
|
||||
cert.sign(key, "sha256")
|
||||
return cert
|
||||
end if defined?(OpenSSL::SSL)
|
||||
|
||||
def test_connect
|
||||
# Testing CONNECT to proxy server
|
||||
#
|
||||
# client -----------> proxy -----------> https
|
||||
# 1. CONNECT establish TCP
|
||||
# 2. ---- establish SSL session --->
|
||||
# 3. ------- GET or POST ---------->
|
||||
#
|
||||
key = TEST_KEY_RSA2048
|
||||
cert = make_certificate(key, "127.0.0.1")
|
||||
s_config = {
|
||||
:SSLEnable =>true,
|
||||
:ServerName => "localhost",
|
||||
:SSLCertificate => cert,
|
||||
:SSLPrivateKey => key,
|
||||
}
|
||||
config = {
|
||||
:ServerName => "localhost.localdomain",
|
||||
:RequestCallback => Proc.new{|req, res|
|
||||
assert_equal("CONNECT", req.request_method)
|
||||
},
|
||||
}
|
||||
TestWEBrick.start_httpserver(s_config){|s_server, s_addr, s_port, s_log|
|
||||
s_server.mount_proc("/"){|req, res|
|
||||
res.body = "SSL #{req.request_method} #{req.path} #{req.body}"
|
||||
}
|
||||
TestWEBrick.start_httpproxy(config){|server, addr, port, log|
|
||||
http = Net::HTTP.new("127.0.0.1", s_port, addr, port)
|
||||
http.use_ssl = true
|
||||
http.verify_callback = Proc.new do |preverify_ok, store_ctx|
|
||||
store_ctx.current_cert.to_der == cert.to_der
|
||||
end
|
||||
|
||||
req = Net::HTTP::Get.new("/")
|
||||
req["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
http.request(req){|res|
|
||||
assert_equal("SSL GET / ", res.body, s_log.call + log.call)
|
||||
}
|
||||
|
||||
req = Net::HTTP::Post.new("/")
|
||||
req["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
req.body = "post-data"
|
||||
http.request(req){|res|
|
||||
assert_equal("SSL POST / post-data", res.body, s_log.call + log.call)
|
||||
}
|
||||
}
|
||||
}
|
||||
end if defined?(OpenSSL::SSL)
|
||||
|
||||
def test_upstream_proxy
|
||||
return if /mswin/ =~ RUBY_PLATFORM && ENV.key?('GITHUB_ACTIONS') # not working from the beginning
|
||||
# Testing GET or POST through the upstream proxy server
|
||||
# Note that the upstream proxy server works as the origin server.
|
||||
# +------+
|
||||
# V |
|
||||
# client -------> proxy -------> proxy ---+
|
||||
# GET / POST GET / POST GET / POST
|
||||
#
|
||||
up_proxy_handler_called = up_request_handler_called = 0
|
||||
proxy_handler_called = request_handler_called = 0
|
||||
up_config = {
|
||||
:ServerName => "localhost.localdomain",
|
||||
:ProxyContentHandler => Proc.new{|req, res| up_proxy_handler_called += 1},
|
||||
:RequestCallback => Proc.new{|req, res| up_request_handler_called += 1}
|
||||
}
|
||||
TestWEBrick.start_httpproxy(up_config){|up_server, up_addr, up_port, up_log|
|
||||
up_server.mount_proc("/"){|req, res|
|
||||
res.body = "#{req.request_method} #{req.path} #{req.body}"
|
||||
}
|
||||
config = {
|
||||
:ServerName => "localhost.localdomain",
|
||||
:ProxyURI => URI.parse("http://localhost:#{up_port}"),
|
||||
:ProxyContentHandler => Proc.new{|req, res| proxy_handler_called += 1},
|
||||
:RequestCallback => Proc.new{|req, res| request_handler_called += 1},
|
||||
}
|
||||
TestWEBrick.start_httpproxy(config){|server, addr, port, log|
|
||||
http = Net::HTTP.new(up_addr, up_port, addr, port)
|
||||
|
||||
req = Net::HTTP::Get.new("/")
|
||||
http.request(req){|res|
|
||||
skip res.message unless res.code == '200'
|
||||
via = res["via"].split(/,\s+/)
|
||||
assert(via.include?("1.1 localhost.localdomain:#{up_port}"), up_log.call + log.call)
|
||||
assert(via.include?("1.1 localhost.localdomain:#{port}"), up_log.call + log.call)
|
||||
assert_equal("GET / ", res.body)
|
||||
}
|
||||
assert_equal(1, up_proxy_handler_called, up_log.call + log.call)
|
||||
assert_equal(2, up_request_handler_called, up_log.call + log.call)
|
||||
assert_equal(1, proxy_handler_called, up_log.call + log.call)
|
||||
assert_equal(1, request_handler_called, up_log.call + log.call)
|
||||
|
||||
req = Net::HTTP::Head.new("/")
|
||||
http.request(req){|res|
|
||||
via = res["via"].split(/,\s+/)
|
||||
assert(via.include?("1.1 localhost.localdomain:#{up_port}"), up_log.call + log.call)
|
||||
assert(via.include?("1.1 localhost.localdomain:#{port}"), up_log.call + log.call)
|
||||
assert_nil(res.body, up_log.call + log.call)
|
||||
}
|
||||
assert_equal(2, up_proxy_handler_called, up_log.call + log.call)
|
||||
assert_equal(4, up_request_handler_called, up_log.call + log.call)
|
||||
assert_equal(2, proxy_handler_called, up_log.call + log.call)
|
||||
assert_equal(2, request_handler_called, up_log.call + log.call)
|
||||
|
||||
req = Net::HTTP::Post.new("/")
|
||||
req.body = "post-data"
|
||||
req.content_type = "application/x-www-form-urlencoded"
|
||||
http.request(req){|res|
|
||||
via = res["via"].split(/,\s+/)
|
||||
assert(via.include?("1.1 localhost.localdomain:#{up_port}"), up_log.call + log.call)
|
||||
assert(via.include?("1.1 localhost.localdomain:#{port}"), up_log.call + log.call)
|
||||
assert_equal("POST / post-data", res.body, up_log.call + log.call)
|
||||
}
|
||||
assert_equal(3, up_proxy_handler_called, up_log.call + log.call)
|
||||
assert_equal(6, up_request_handler_called, up_log.call + log.call)
|
||||
assert_equal(3, proxy_handler_called, up_log.call + log.call)
|
||||
assert_equal(3, request_handler_called, up_log.call + log.call)
|
||||
|
||||
if defined?(OpenSSL::SSL)
|
||||
# Testing CONNECT to the upstream proxy server
|
||||
#
|
||||
# client -------> proxy -------> proxy -------> https
|
||||
# 1. CONNECT CONNECT establish TCP
|
||||
# 2. -------- establish SSL session ------>
|
||||
# 3. ---------- GET or POST -------------->
|
||||
#
|
||||
key = TEST_KEY_RSA2048
|
||||
cert = make_certificate(key, "127.0.0.1")
|
||||
s_config = {
|
||||
:SSLEnable =>true,
|
||||
:ServerName => "localhost",
|
||||
:SSLCertificate => cert,
|
||||
:SSLPrivateKey => key,
|
||||
}
|
||||
TestWEBrick.start_httpserver(s_config){|s_server, s_addr, s_port, s_log|
|
||||
s_server.mount_proc("/"){|req2, res|
|
||||
res.body = "SSL #{req2.request_method} #{req2.path} #{req2.body}"
|
||||
}
|
||||
http = Net::HTTP.new("127.0.0.1", s_port, addr, port, up_log.call + log.call + s_log.call)
|
||||
http.use_ssl = true
|
||||
http.verify_callback = Proc.new do |preverify_ok, store_ctx|
|
||||
store_ctx.current_cert.to_der == cert.to_der
|
||||
end
|
||||
|
||||
req2 = Net::HTTP::Get.new("/")
|
||||
http.request(req2){|res|
|
||||
assert_equal("SSL GET / ", res.body, up_log.call + log.call + s_log.call)
|
||||
}
|
||||
|
||||
req2 = Net::HTTP::Post.new("/")
|
||||
req2.body = "post-data"
|
||||
req2.content_type = "application/x-www-form-urlencoded"
|
||||
http.request(req2){|res|
|
||||
assert_equal("SSL POST / post-data", res.body, up_log.call + log.call + s_log.call)
|
||||
}
|
||||
}
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
if defined?(OpenSSL::SSL)
|
||||
TEST_KEY_RSA2048 = OpenSSL::PKey.read <<-_end_of_pem_
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAuV9ht9J7k4NBs38jOXvvTKY9gW8nLICSno5EETR1cuF7i4pN
|
||||
s9I1QJGAFAX0BEO4KbzXmuOvfCpD3CU+Slp1enenfzq/t/e/1IRW0wkJUJUFQign
|
||||
4CtrkJL+P07yx18UjyPlBXb81ApEmAB5mrJVSrWmqbjs07JbuS4QQGGXLc+Su96D
|
||||
kYKmSNVjBiLxVVSpyZfAY3hD37d60uG+X8xdW5v68JkRFIhdGlb6JL8fllf/A/bl
|
||||
NwdJOhVr9mESHhwGjwfSeTDPfd8ZLE027E5lyAVX9KZYcU00mOX+fdxOSnGqS/8J
|
||||
DRh0EPHDL15RcJjV2J6vZjPb0rOYGDoMcH+94wIDAQABAoIBAAzsamqfYQAqwXTb
|
||||
I0CJtGg6msUgU7HVkOM+9d3hM2L791oGHV6xBAdpXW2H8LgvZHJ8eOeSghR8+dgq
|
||||
PIqAffo4x1Oma+FOg3A0fb0evyiACyrOk+EcBdbBeLo/LcvahBtqnDfiUMQTpy6V
|
||||
seSoFCwuN91TSCeGIsDpRjbG1vxZgtx+uI+oH5+ytqJOmfCksRDCkMglGkzyfcl0
|
||||
Xc5CUhIJ0my53xijEUQl19rtWdMnNnnkdbG8PT3LZlOta5Do86BElzUYka0C6dUc
|
||||
VsBDQ0Nup0P6rEQgy7tephHoRlUGTYamsajGJaAo1F3IQVIrRSuagi7+YpSpCqsW
|
||||
wORqorkCgYEA7RdX6MDVrbw7LePnhyuaqTiMK+055/R1TqhB1JvvxJ1CXk2rDL6G
|
||||
0TLHQ7oGofd5LYiemg4ZVtWdJe43BPZlVgT6lvL/iGo8JnrncB9Da6L7nrq/+Rvj
|
||||
XGjf1qODCK+LmreZWEsaLPURIoR/Ewwxb9J2zd0CaMjeTwafJo1CZvcCgYEAyCgb
|
||||
aqoWvUecX8VvARfuA593Lsi50t4MEArnOXXcd1RnXoZWhbx5rgO8/ATKfXr0BK/n
|
||||
h2GF9PfKzHFm/4V6e82OL7gu/kLy2u9bXN74vOvWFL5NOrOKPM7Kg+9I131kNYOw
|
||||
Ivnr/VtHE5s0dY7JChYWE1F3vArrOw3T00a4CXUCgYEA0SqY+dS2LvIzW4cHCe9k
|
||||
IQqsT0yYm5TFsUEr4sA3xcPfe4cV8sZb9k/QEGYb1+SWWZ+AHPV3UW5fl8kTbSNb
|
||||
v4ng8i8rVVQ0ANbJO9e5CUrepein2MPL0AkOATR8M7t7dGGpvYV0cFk8ZrFx0oId
|
||||
U0PgYDotF/iueBWlbsOM430CgYEAqYI95dFyPI5/AiSkY5queeb8+mQH62sdcCCr
|
||||
vd/w/CZA/K5sbAo4SoTj8dLk4evU6HtIa0DOP63y071eaxvRpTNqLUOgmLh+D6gS
|
||||
Cc7TfLuFrD+WDBatBd5jZ+SoHccVrLR/4L8jeodo5FPW05A+9gnKXEXsTxY4LOUC
|
||||
9bS4e1kCgYAqVXZh63JsMwoaxCYmQ66eJojKa47VNrOeIZDZvd2BPVf30glBOT41
|
||||
gBoDG3WMPZoQj9pb7uMcrnvs4APj2FIhMU8U15LcPAj59cD6S6rWnAxO8NFK7HQG
|
||||
4Jxg3JNNf8ErQoCHb1B3oVdXJkmbJkARoDpBKmTCgKtP8ADYLmVPQw==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
_end_of_pem_
|
||||
end
|
||||
end
|
|
@ -1,488 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "webrick"
|
||||
require "stringio"
|
||||
require "test/unit"
|
||||
|
||||
class TestWEBrickHTTPRequest < Test::Unit::TestCase
|
||||
def teardown
|
||||
WEBrick::Utils::TimeoutHandler.terminate
|
||||
super
|
||||
end
|
||||
|
||||
def test_simple_request
|
||||
msg = <<-_end_of_message_
|
||||
GET /
|
||||
_end_of_message_
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg))
|
||||
assert(req.meta_vars) # fails if @header was not initialized and iteration is attempted on the nil reference
|
||||
end
|
||||
|
||||
def test_parse_09
|
||||
msg = <<-_end_of_message_
|
||||
GET /
|
||||
foobar # HTTP/0.9 request don't have header nor entity body.
|
||||
_end_of_message_
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
assert_equal("GET", req.request_method)
|
||||
assert_equal("/", req.unparsed_uri)
|
||||
assert_equal(WEBrick::HTTPVersion.new("0.9"), req.http_version)
|
||||
assert_equal(WEBrick::Config::HTTP[:ServerName], req.host)
|
||||
assert_equal(80, req.port)
|
||||
assert_equal(false, req.keep_alive?)
|
||||
assert_equal(nil, req.body)
|
||||
assert(req.query.empty?)
|
||||
end
|
||||
|
||||
def test_parse_10
|
||||
msg = <<-_end_of_message_
|
||||
GET / HTTP/1.0
|
||||
|
||||
_end_of_message_
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
assert_equal("GET", req.request_method)
|
||||
assert_equal("/", req.unparsed_uri)
|
||||
assert_equal(WEBrick::HTTPVersion.new("1.0"), req.http_version)
|
||||
assert_equal(WEBrick::Config::HTTP[:ServerName], req.host)
|
||||
assert_equal(80, req.port)
|
||||
assert_equal(false, req.keep_alive?)
|
||||
assert_equal(nil, req.body)
|
||||
assert(req.query.empty?)
|
||||
end
|
||||
|
||||
def test_parse_11
|
||||
msg = <<-_end_of_message_
|
||||
GET /path HTTP/1.1
|
||||
|
||||
_end_of_message_
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
assert_equal("GET", req.request_method)
|
||||
assert_equal("/path", req.unparsed_uri)
|
||||
assert_equal("", req.script_name)
|
||||
assert_equal("/path", req.path_info)
|
||||
assert_equal(WEBrick::HTTPVersion.new("1.1"), req.http_version)
|
||||
assert_equal(WEBrick::Config::HTTP[:ServerName], req.host)
|
||||
assert_equal(80, req.port)
|
||||
assert_equal(true, req.keep_alive?)
|
||||
assert_equal(nil, req.body)
|
||||
assert(req.query.empty?)
|
||||
end
|
||||
|
||||
def test_request_uri_too_large
|
||||
msg = <<-_end_of_message_
|
||||
GET /#{"a"*2084} HTTP/1.1
|
||||
_end_of_message_
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
assert_raise(WEBrick::HTTPStatus::RequestURITooLarge){
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
}
|
||||
end
|
||||
|
||||
def test_parse_headers
|
||||
msg = <<-_end_of_message_
|
||||
GET /path HTTP/1.1
|
||||
Host: test.ruby-lang.org:8080
|
||||
Connection: close
|
||||
Accept: text/*;q=0.3, text/html;q=0.7, text/html;level=1,
|
||||
text/html;level=2;q=0.4, */*;q=0.5
|
||||
Accept-Encoding: compress;q=0.5
|
||||
Accept-Encoding: gzip;q=1.0, identity; q=0.4, *;q=0
|
||||
Accept-Language: en;q=0.5, *; q=0
|
||||
Accept-Language: ja
|
||||
Content-Type: text/plain
|
||||
Content-Length: 7
|
||||
X-Empty-Header:
|
||||
|
||||
foobar
|
||||
_end_of_message_
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
assert_equal(
|
||||
URI.parse("http://test.ruby-lang.org:8080/path"), req.request_uri)
|
||||
assert_equal("test.ruby-lang.org", req.host)
|
||||
assert_equal(8080, req.port)
|
||||
assert_equal(false, req.keep_alive?)
|
||||
assert_equal(
|
||||
%w(text/html;level=1 text/html */* text/html;level=2 text/*),
|
||||
req.accept)
|
||||
assert_equal(%w(gzip compress identity *), req.accept_encoding)
|
||||
assert_equal(%w(ja en *), req.accept_language)
|
||||
assert_equal(7, req.content_length)
|
||||
assert_equal("text/plain", req.content_type)
|
||||
assert_equal("foobar\n", req.body)
|
||||
assert_equal("", req["x-empty-header"])
|
||||
assert_equal(nil, req["x-no-header"])
|
||||
assert(req.query.empty?)
|
||||
end
|
||||
|
||||
def test_parse_header2()
|
||||
msg = <<-_end_of_message_
|
||||
POST /foo/bar/../baz?q=a HTTP/1.0
|
||||
Content-Length: 9
|
||||
User-Agent:
|
||||
FOO BAR
|
||||
BAZ
|
||||
|
||||
hogehoge
|
||||
_end_of_message_
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
assert_equal("POST", req.request_method)
|
||||
assert_equal("/foo/baz", req.path)
|
||||
assert_equal("", req.script_name)
|
||||
assert_equal("/foo/baz", req.path_info)
|
||||
assert_equal("9", req['content-length'])
|
||||
assert_equal("FOO BAR BAZ", req['user-agent'])
|
||||
assert_equal("hogehoge\n", req.body)
|
||||
end
|
||||
|
||||
def test_parse_headers3
|
||||
msg = <<-_end_of_message_
|
||||
GET /path HTTP/1.1
|
||||
Host: test.ruby-lang.org
|
||||
|
||||
_end_of_message_
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
assert_equal(URI.parse("http://test.ruby-lang.org/path"), req.request_uri)
|
||||
assert_equal("test.ruby-lang.org", req.host)
|
||||
assert_equal(80, req.port)
|
||||
|
||||
msg = <<-_end_of_message_
|
||||
GET /path HTTP/1.1
|
||||
Host: 192.168.1.1
|
||||
|
||||
_end_of_message_
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
assert_equal(URI.parse("http://192.168.1.1/path"), req.request_uri)
|
||||
assert_equal("192.168.1.1", req.host)
|
||||
assert_equal(80, req.port)
|
||||
|
||||
msg = <<-_end_of_message_
|
||||
GET /path HTTP/1.1
|
||||
Host: [fe80::208:dff:feef:98c7]
|
||||
|
||||
_end_of_message_
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
assert_equal(URI.parse("http://[fe80::208:dff:feef:98c7]/path"),
|
||||
req.request_uri)
|
||||
assert_equal("[fe80::208:dff:feef:98c7]", req.host)
|
||||
assert_equal(80, req.port)
|
||||
|
||||
msg = <<-_end_of_message_
|
||||
GET /path HTTP/1.1
|
||||
Host: 192.168.1.1:8080
|
||||
|
||||
_end_of_message_
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
assert_equal(URI.parse("http://192.168.1.1:8080/path"), req.request_uri)
|
||||
assert_equal("192.168.1.1", req.host)
|
||||
assert_equal(8080, req.port)
|
||||
|
||||
msg = <<-_end_of_message_
|
||||
GET /path HTTP/1.1
|
||||
Host: [fe80::208:dff:feef:98c7]:8080
|
||||
|
||||
_end_of_message_
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
assert_equal(URI.parse("http://[fe80::208:dff:feef:98c7]:8080/path"),
|
||||
req.request_uri)
|
||||
assert_equal("[fe80::208:dff:feef:98c7]", req.host)
|
||||
assert_equal(8080, req.port)
|
||||
end
|
||||
|
||||
def test_parse_get_params
|
||||
param = "foo=1;foo=2;foo=3;bar=x"
|
||||
msg = <<-_end_of_message_
|
||||
GET /path?#{param} HTTP/1.1
|
||||
Host: test.ruby-lang.org:8080
|
||||
|
||||
_end_of_message_
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
query = req.query
|
||||
assert_equal("1", query["foo"])
|
||||
assert_equal(["1", "2", "3"], query["foo"].to_ary)
|
||||
assert_equal(["1", "2", "3"], query["foo"].list)
|
||||
assert_equal("x", query["bar"])
|
||||
assert_equal(["x"], query["bar"].list)
|
||||
end
|
||||
|
||||
def test_parse_post_params
|
||||
param = "foo=1;foo=2;foo=3;bar=x"
|
||||
msg = <<-_end_of_message_
|
||||
POST /path?foo=x;foo=y;foo=z;bar=1 HTTP/1.1
|
||||
Host: test.ruby-lang.org:8080
|
||||
Content-Length: #{param.size}
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
#{param}
|
||||
_end_of_message_
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
query = req.query
|
||||
assert_equal("1", query["foo"])
|
||||
assert_equal(["1", "2", "3"], query["foo"].to_ary)
|
||||
assert_equal(["1", "2", "3"], query["foo"].list)
|
||||
assert_equal("x", query["bar"])
|
||||
assert_equal(["x"], query["bar"].list)
|
||||
end
|
||||
|
||||
def test_chunked
|
||||
crlf = "\x0d\x0a"
|
||||
expect = File.binread(__FILE__).freeze
|
||||
msg = <<-_end_of_message_
|
||||
POST /path HTTP/1.1
|
||||
Host: test.ruby-lang.org:8080
|
||||
Transfer-Encoding: chunked
|
||||
|
||||
_end_of_message_
|
||||
msg.gsub!(/^ {6}/, "")
|
||||
File.open(__FILE__){|io|
|
||||
while chunk = io.read(100)
|
||||
msg << chunk.size.to_s(16) << crlf
|
||||
msg << chunk << crlf
|
||||
end
|
||||
}
|
||||
msg << "0" << crlf
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg))
|
||||
assert_equal(expect, req.body)
|
||||
|
||||
# chunked req.body_reader
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg))
|
||||
dst = StringIO.new
|
||||
IO.copy_stream(req.body_reader, dst)
|
||||
assert_equal(expect, dst.string)
|
||||
end
|
||||
|
||||
def test_forwarded
|
||||
msg = <<-_end_of_message_
|
||||
GET /foo HTTP/1.1
|
||||
Host: localhost:10080
|
||||
User-Agent: w3m/0.5.2
|
||||
X-Forwarded-For: 123.123.123.123
|
||||
X-Forwarded-Host: forward.example.com
|
||||
X-Forwarded-Server: server.example.com
|
||||
Connection: Keep-Alive
|
||||
|
||||
_end_of_message_
|
||||
msg.gsub!(/^ {6}/, "")
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg))
|
||||
assert_equal("server.example.com", req.server_name)
|
||||
assert_equal("http://forward.example.com/foo", req.request_uri.to_s)
|
||||
assert_equal("forward.example.com", req.host)
|
||||
assert_equal(80, req.port)
|
||||
assert_equal("123.123.123.123", req.remote_ip)
|
||||
assert(!req.ssl?)
|
||||
|
||||
msg = <<-_end_of_message_
|
||||
GET /foo HTTP/1.1
|
||||
Host: localhost:10080
|
||||
User-Agent: w3m/0.5.2
|
||||
X-Forwarded-For: 192.168.1.10, 172.16.1.1, 123.123.123.123
|
||||
X-Forwarded-Host: forward.example.com:8080
|
||||
X-Forwarded-Server: server.example.com
|
||||
Connection: Keep-Alive
|
||||
|
||||
_end_of_message_
|
||||
msg.gsub!(/^ {6}/, "")
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg))
|
||||
assert_equal("server.example.com", req.server_name)
|
||||
assert_equal("http://forward.example.com:8080/foo", req.request_uri.to_s)
|
||||
assert_equal("forward.example.com", req.host)
|
||||
assert_equal(8080, req.port)
|
||||
assert_equal("123.123.123.123", req.remote_ip)
|
||||
assert(!req.ssl?)
|
||||
|
||||
msg = <<-_end_of_message_
|
||||
GET /foo HTTP/1.1
|
||||
Host: localhost:10080
|
||||
Client-IP: 234.234.234.234
|
||||
X-Forwarded-Proto: https, http
|
||||
X-Forwarded-For: 192.168.1.10, 10.0.0.1, 123.123.123.123
|
||||
X-Forwarded-Host: forward.example.com
|
||||
X-Forwarded-Server: server.example.com
|
||||
X-Requested-With: XMLHttpRequest
|
||||
Connection: Keep-Alive
|
||||
|
||||
_end_of_message_
|
||||
msg.gsub!(/^ {6}/, "")
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg))
|
||||
assert_equal("server.example.com", req.server_name)
|
||||
assert_equal("https://forward.example.com/foo", req.request_uri.to_s)
|
||||
assert_equal("forward.example.com", req.host)
|
||||
assert_equal(443, req.port)
|
||||
assert_equal("234.234.234.234", req.remote_ip)
|
||||
assert(req.ssl?)
|
||||
|
||||
msg = <<-_end_of_message_
|
||||
GET /foo HTTP/1.1
|
||||
Host: localhost:10080
|
||||
Client-IP: 234.234.234.234
|
||||
X-Forwarded-Proto: https
|
||||
X-Forwarded-For: 192.168.1.10
|
||||
X-Forwarded-Host: forward1.example.com:1234, forward2.example.com:5678
|
||||
X-Forwarded-Server: server1.example.com, server2.example.com
|
||||
X-Requested-With: XMLHttpRequest
|
||||
Connection: Keep-Alive
|
||||
|
||||
_end_of_message_
|
||||
msg.gsub!(/^ {6}/, "")
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg))
|
||||
assert_equal("server1.example.com", req.server_name)
|
||||
assert_equal("https://forward1.example.com:1234/foo", req.request_uri.to_s)
|
||||
assert_equal("forward1.example.com", req.host)
|
||||
assert_equal(1234, req.port)
|
||||
assert_equal("234.234.234.234", req.remote_ip)
|
||||
assert(req.ssl?)
|
||||
|
||||
msg = <<-_end_of_message_
|
||||
GET /foo HTTP/1.1
|
||||
Host: localhost:10080
|
||||
Client-IP: 234.234.234.234
|
||||
X-Forwarded-Proto: https
|
||||
X-Forwarded-For: 192.168.1.10
|
||||
X-Forwarded-Host: [fd20:8b1e:b255:8154:250:56ff:fea8:4d84], forward2.example.com:5678
|
||||
X-Forwarded-Server: server1.example.com, server2.example.com
|
||||
X-Requested-With: XMLHttpRequest
|
||||
Connection: Keep-Alive
|
||||
|
||||
_end_of_message_
|
||||
msg.gsub!(/^ {6}/, "")
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg))
|
||||
assert_equal("server1.example.com", req.server_name)
|
||||
assert_equal("https://[fd20:8b1e:b255:8154:250:56ff:fea8:4d84]/foo", req.request_uri.to_s)
|
||||
assert_equal("[fd20:8b1e:b255:8154:250:56ff:fea8:4d84]", req.host)
|
||||
assert_equal(443, req.port)
|
||||
assert_equal("234.234.234.234", req.remote_ip)
|
||||
assert(req.ssl?)
|
||||
|
||||
msg = <<-_end_of_message_
|
||||
GET /foo HTTP/1.1
|
||||
Host: localhost:10080
|
||||
Client-IP: 234.234.234.234
|
||||
X-Forwarded-Proto: https
|
||||
X-Forwarded-For: 192.168.1.10
|
||||
X-Forwarded-Host: [fd20:8b1e:b255:8154:250:56ff:fea8:4d84]:1234, forward2.example.com:5678
|
||||
X-Forwarded-Server: server1.example.com, server2.example.com
|
||||
X-Requested-With: XMLHttpRequest
|
||||
Connection: Keep-Alive
|
||||
|
||||
_end_of_message_
|
||||
msg.gsub!(/^ {6}/, "")
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg))
|
||||
assert_equal("server1.example.com", req.server_name)
|
||||
assert_equal("https://[fd20:8b1e:b255:8154:250:56ff:fea8:4d84]:1234/foo", req.request_uri.to_s)
|
||||
assert_equal("[fd20:8b1e:b255:8154:250:56ff:fea8:4d84]", req.host)
|
||||
assert_equal(1234, req.port)
|
||||
assert_equal("234.234.234.234", req.remote_ip)
|
||||
assert(req.ssl?)
|
||||
end
|
||||
|
||||
def test_continue_sent
|
||||
msg = <<-_end_of_message_
|
||||
POST /path HTTP/1.1
|
||||
Expect: 100-continue
|
||||
|
||||
_end_of_message_
|
||||
msg.gsub!(/^ {6}/, "")
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg))
|
||||
assert req['expect']
|
||||
l = msg.size
|
||||
req.continue
|
||||
assert_not_equal l, msg.size
|
||||
assert_match(/HTTP\/1.1 100 continue\r\n\r\n\z/, msg)
|
||||
assert !req['expect']
|
||||
end
|
||||
|
||||
def test_continue_not_sent
|
||||
msg = <<-_end_of_message_
|
||||
POST /path HTTP/1.1
|
||||
|
||||
_end_of_message_
|
||||
msg.gsub!(/^ {6}/, "")
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg))
|
||||
assert !req['expect']
|
||||
l = msg.size
|
||||
req.continue
|
||||
assert_equal l, msg.size
|
||||
end
|
||||
|
||||
def test_empty_post
|
||||
msg = <<-_end_of_message_
|
||||
POST /path?foo=x;foo=y;foo=z;bar=1 HTTP/1.1
|
||||
Host: test.ruby-lang.org:8080
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
_end_of_message_
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
req.body
|
||||
end
|
||||
|
||||
def test_bad_messages
|
||||
param = "foo=1;foo=2;foo=3;bar=x"
|
||||
msg = <<-_end_of_message_
|
||||
POST /path?foo=x;foo=y;foo=z;bar=1 HTTP/1.1
|
||||
Host: test.ruby-lang.org:8080
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
#{param}
|
||||
_end_of_message_
|
||||
assert_raise(WEBrick::HTTPStatus::LengthRequired){
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
req.body
|
||||
}
|
||||
|
||||
msg = <<-_end_of_message_
|
||||
POST /path?foo=x;foo=y;foo=z;bar=1 HTTP/1.1
|
||||
Host: test.ruby-lang.org:8080
|
||||
Content-Length: 100000
|
||||
|
||||
body is too short.
|
||||
_end_of_message_
|
||||
assert_raise(WEBrick::HTTPStatus::BadRequest){
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
req.body
|
||||
}
|
||||
|
||||
msg = <<-_end_of_message_
|
||||
POST /path?foo=x;foo=y;foo=z;bar=1 HTTP/1.1
|
||||
Host: test.ruby-lang.org:8080
|
||||
Transfer-Encoding: foobar
|
||||
|
||||
body is too short.
|
||||
_end_of_message_
|
||||
assert_raise(WEBrick::HTTPStatus::NotImplemented){
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(msg.gsub(/^ {6}/, "")))
|
||||
req.body
|
||||
}
|
||||
end
|
||||
|
||||
def test_eof_raised_when_line_is_nil
|
||||
assert_raise(WEBrick::HTTPStatus::EOFError) {
|
||||
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
||||
req.parse(StringIO.new(""))
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,282 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "webrick"
|
||||
require "test/unit"
|
||||
require "stringio"
|
||||
require "net/http"
|
||||
|
||||
module WEBrick
|
||||
class TestHTTPResponse < Test::Unit::TestCase
|
||||
class FakeLogger
|
||||
attr_reader :messages
|
||||
|
||||
def initialize
|
||||
@messages = []
|
||||
end
|
||||
|
||||
def warn msg
|
||||
@messages << msg
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :config, :logger, :res
|
||||
|
||||
def setup
|
||||
super
|
||||
@logger = FakeLogger.new
|
||||
@config = Config::HTTP
|
||||
@config[:Logger] = logger
|
||||
@res = HTTPResponse.new config
|
||||
@res.keep_alive = true
|
||||
end
|
||||
|
||||
def test_prevent_response_splitting_headers_crlf
|
||||
res['X-header'] = "malicious\r\nCookie: cracked_indicator_for_test"
|
||||
io = StringIO.new
|
||||
res.send_response io
|
||||
io.rewind
|
||||
res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io))
|
||||
assert_equal '500', res.code
|
||||
refute_match 'cracked_indicator_for_test', io.string
|
||||
end
|
||||
|
||||
def test_prevent_response_splitting_cookie_headers_crlf
|
||||
user_input = "malicious\r\nCookie: cracked_indicator_for_test"
|
||||
res.cookies << WEBrick::Cookie.new('author', user_input)
|
||||
io = StringIO.new
|
||||
res.send_response io
|
||||
io.rewind
|
||||
res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io))
|
||||
assert_equal '500', res.code
|
||||
refute_match 'cracked_indicator_for_test', io.string
|
||||
end
|
||||
|
||||
def test_prevent_response_splitting_headers_cr
|
||||
res['X-header'] = "malicious\rCookie: cracked_indicator_for_test"
|
||||
io = StringIO.new
|
||||
res.send_response io
|
||||
io.rewind
|
||||
res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io))
|
||||
assert_equal '500', res.code
|
||||
refute_match 'cracked_indicator_for_test', io.string
|
||||
end
|
||||
|
||||
def test_prevent_response_splitting_cookie_headers_cr
|
||||
user_input = "malicious\rCookie: cracked_indicator_for_test"
|
||||
res.cookies << WEBrick::Cookie.new('author', user_input)
|
||||
io = StringIO.new
|
||||
res.send_response io
|
||||
io.rewind
|
||||
res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io))
|
||||
assert_equal '500', res.code
|
||||
refute_match 'cracked_indicator_for_test', io.string
|
||||
end
|
||||
|
||||
def test_prevent_response_splitting_headers_lf
|
||||
res['X-header'] = "malicious\nCookie: cracked_indicator_for_test"
|
||||
io = StringIO.new
|
||||
res.send_response io
|
||||
io.rewind
|
||||
res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io))
|
||||
assert_equal '500', res.code
|
||||
refute_match 'cracked_indicator_for_test', io.string
|
||||
end
|
||||
|
||||
def test_prevent_response_splitting_cookie_headers_lf
|
||||
user_input = "malicious\nCookie: cracked_indicator_for_test"
|
||||
res.cookies << WEBrick::Cookie.new('author', user_input)
|
||||
io = StringIO.new
|
||||
res.send_response io
|
||||
io.rewind
|
||||
res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io))
|
||||
assert_equal '500', res.code
|
||||
refute_match 'cracked_indicator_for_test', io.string
|
||||
end
|
||||
|
||||
def test_set_redirect_response_splitting
|
||||
url = "malicious\r\nCookie: cracked_indicator_for_test"
|
||||
assert_raise(URI::InvalidURIError) do
|
||||
res.set_redirect(WEBrick::HTTPStatus::MultipleChoices, url)
|
||||
end
|
||||
end
|
||||
|
||||
def test_set_redirect_html_injection
|
||||
url = 'http://example.com////?a</a><head></head><body><img src=1></body>'
|
||||
assert_raise(WEBrick::HTTPStatus::MultipleChoices) do
|
||||
res.set_redirect(WEBrick::HTTPStatus::MultipleChoices, url)
|
||||
end
|
||||
res.status = 300
|
||||
io = StringIO.new
|
||||
res.send_response(io)
|
||||
io.rewind
|
||||
res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io))
|
||||
assert_equal '300', res.code
|
||||
refute_match(/<img/, io.string)
|
||||
end
|
||||
|
||||
def test_304_does_not_log_warning
|
||||
res.status = 304
|
||||
res.setup_header
|
||||
assert_equal 0, logger.messages.length
|
||||
end
|
||||
|
||||
def test_204_does_not_log_warning
|
||||
res.status = 204
|
||||
res.setup_header
|
||||
|
||||
assert_equal 0, logger.messages.length
|
||||
end
|
||||
|
||||
def test_1xx_does_not_log_warnings
|
||||
res.status = 105
|
||||
res.setup_header
|
||||
|
||||
assert_equal 0, logger.messages.length
|
||||
end
|
||||
|
||||
def test_200_chunked_does_not_set_content_length
|
||||
res.chunked = false
|
||||
res["Transfer-Encoding"] = 'chunked'
|
||||
res.setup_header
|
||||
assert_nil res.header.fetch('content-length', nil)
|
||||
end
|
||||
|
||||
def test_send_body_io
|
||||
IO.pipe {|body_r, body_w|
|
||||
body_w.write 'hello'
|
||||
body_w.close
|
||||
|
||||
@res.body = body_r
|
||||
|
||||
IO.pipe {|r, w|
|
||||
|
||||
@res.send_body w
|
||||
|
||||
w.close
|
||||
|
||||
assert_equal 'hello', r.read
|
||||
}
|
||||
}
|
||||
assert_equal 0, logger.messages.length
|
||||
end
|
||||
|
||||
def test_send_body_string
|
||||
@res.body = 'hello'
|
||||
|
||||
IO.pipe {|r, w|
|
||||
@res.send_body w
|
||||
|
||||
w.close
|
||||
|
||||
assert_equal 'hello', r.read
|
||||
}
|
||||
assert_equal 0, logger.messages.length
|
||||
end
|
||||
|
||||
def test_send_body_string_io
|
||||
@res.body = StringIO.new 'hello'
|
||||
|
||||
IO.pipe {|r, w|
|
||||
@res.send_body w
|
||||
|
||||
w.close
|
||||
|
||||
assert_equal 'hello', r.read
|
||||
}
|
||||
assert_equal 0, logger.messages.length
|
||||
end
|
||||
|
||||
def test_send_body_io_chunked
|
||||
@res.chunked = true
|
||||
|
||||
IO.pipe {|body_r, body_w|
|
||||
|
||||
body_w.write 'hello'
|
||||
body_w.close
|
||||
|
||||
@res.body = body_r
|
||||
|
||||
IO.pipe {|r, w|
|
||||
@res.send_body w
|
||||
|
||||
w.close
|
||||
|
||||
r.binmode
|
||||
assert_equal "5\r\nhello\r\n0\r\n\r\n", r.read
|
||||
}
|
||||
}
|
||||
assert_equal 0, logger.messages.length
|
||||
end
|
||||
|
||||
def test_send_body_string_chunked
|
||||
@res.chunked = true
|
||||
|
||||
@res.body = 'hello'
|
||||
|
||||
IO.pipe {|r, w|
|
||||
@res.send_body w
|
||||
|
||||
w.close
|
||||
|
||||
r.binmode
|
||||
assert_equal "5\r\nhello\r\n0\r\n\r\n", r.read
|
||||
}
|
||||
assert_equal 0, logger.messages.length
|
||||
end
|
||||
|
||||
def test_send_body_string_io_chunked
|
||||
@res.chunked = true
|
||||
|
||||
@res.body = StringIO.new 'hello'
|
||||
|
||||
IO.pipe {|r, w|
|
||||
@res.send_body w
|
||||
|
||||
w.close
|
||||
|
||||
r.binmode
|
||||
assert_equal "5\r\nhello\r\n0\r\n\r\n", r.read
|
||||
}
|
||||
assert_equal 0, logger.messages.length
|
||||
end
|
||||
|
||||
def test_send_body_proc
|
||||
@res.body = Proc.new { |out| out.write('hello') }
|
||||
IO.pipe do |r, w|
|
||||
@res.send_body(w)
|
||||
w.close
|
||||
r.binmode
|
||||
assert_equal 'hello', r.read
|
||||
end
|
||||
assert_equal 0, logger.messages.length
|
||||
end
|
||||
|
||||
def test_send_body_proc_chunked
|
||||
@res.body = Proc.new { |out| out.write('hello') }
|
||||
@res.chunked = true
|
||||
IO.pipe do |r, w|
|
||||
@res.send_body(w)
|
||||
w.close
|
||||
r.binmode
|
||||
assert_equal "5\r\nhello\r\n0\r\n\r\n", r.read
|
||||
end
|
||||
assert_equal 0, logger.messages.length
|
||||
end
|
||||
|
||||
def test_set_error
|
||||
status = 400
|
||||
message = 'missing attribute'
|
||||
@res.status = status
|
||||
error = WEBrick::HTTPStatus[status].new(message)
|
||||
body = @res.set_error(error)
|
||||
assert_match(/#{@res.reason_phrase}/, body)
|
||||
assert_match(/#{message}/, body)
|
||||
end
|
||||
|
||||
def test_no_extraneous_space
|
||||
[200, 300, 400, 500].each do |status|
|
||||
@res.status = status
|
||||
assert_match(/\S\r\n/, @res.status_line)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,112 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "test/unit"
|
||||
require "net/http"
|
||||
require "webrick"
|
||||
require "webrick/https"
|
||||
require "webrick/utils"
|
||||
require_relative "utils"
|
||||
|
||||
class TestWEBrickHTTPS < Test::Unit::TestCase
|
||||
empty_log = Object.new
|
||||
def empty_log.<<(str)
|
||||
assert_equal('', str)
|
||||
self
|
||||
end
|
||||
NoLog = WEBrick::Log.new(empty_log, WEBrick::BasicLog::WARN)
|
||||
|
||||
class HTTPSNITest < ::Net::HTTP
|
||||
attr_accessor :sni_hostname
|
||||
|
||||
def ssl_socket_connect(s, timeout)
|
||||
s.hostname = sni_hostname
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
WEBrick::Utils::TimeoutHandler.terminate
|
||||
super
|
||||
end
|
||||
|
||||
def https_get(addr, port, hostname, path, verifyname = nil)
|
||||
subject = nil
|
||||
http = HTTPSNITest.new(addr, port)
|
||||
http.use_ssl = true
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
http.verify_callback = proc { |x, store| subject = store.chain[0].subject.to_s; x }
|
||||
http.sni_hostname = hostname
|
||||
req = Net::HTTP::Get.new(path)
|
||||
req["Host"] = "#{hostname}:#{port}"
|
||||
response = http.start { http.request(req).body }
|
||||
assert_equal("/CN=#{verifyname || hostname}", subject)
|
||||
response
|
||||
end
|
||||
|
||||
def test_sni
|
||||
config = {
|
||||
:ServerName => "localhost",
|
||||
:SSLEnable => true,
|
||||
:SSLCertName => "/CN=localhost",
|
||||
}
|
||||
TestWEBrick.start_httpserver(config){|server, addr, port, log|
|
||||
server.mount_proc("/") {|req, res| res.body = "master" }
|
||||
|
||||
# catch stderr in create_self_signed_cert
|
||||
stderr_buffer = StringIO.new
|
||||
old_stderr, $stderr = $stderr, stderr_buffer
|
||||
|
||||
begin
|
||||
vhost_config1 = {
|
||||
:ServerName => "vhost1",
|
||||
:Port => port,
|
||||
:DoNotListen => true,
|
||||
:Logger => NoLog,
|
||||
:AccessLog => [],
|
||||
:SSLEnable => true,
|
||||
:SSLCertName => "/CN=vhost1",
|
||||
}
|
||||
vhost1 = WEBrick::HTTPServer.new(vhost_config1)
|
||||
vhost1.mount_proc("/") {|req, res| res.body = "vhost1" }
|
||||
server.virtual_host(vhost1)
|
||||
|
||||
vhost_config2 = {
|
||||
:ServerName => "vhost2",
|
||||
:ServerAlias => ["vhost2alias"],
|
||||
:Port => port,
|
||||
:DoNotListen => true,
|
||||
:Logger => NoLog,
|
||||
:AccessLog => [],
|
||||
:SSLEnable => true,
|
||||
:SSLCertName => "/CN=vhost2",
|
||||
}
|
||||
vhost2 = WEBrick::HTTPServer.new(vhost_config2)
|
||||
vhost2.mount_proc("/") {|req, res| res.body = "vhost2" }
|
||||
server.virtual_host(vhost2)
|
||||
ensure
|
||||
# restore stderr
|
||||
$stderr = old_stderr
|
||||
end
|
||||
|
||||
assert_match(/\A([.+*]+\n)+\z/, stderr_buffer.string)
|
||||
assert_equal("master", https_get(addr, port, "localhost", "/localhost"))
|
||||
assert_equal("master", https_get(addr, port, "unknown", "/unknown", "localhost"))
|
||||
assert_equal("vhost1", https_get(addr, port, "vhost1", "/vhost1"))
|
||||
assert_equal("vhost2", https_get(addr, port, "vhost2", "/vhost2"))
|
||||
assert_equal("vhost2", https_get(addr, port, "vhost2alias", "/vhost2alias", "vhost2"))
|
||||
}
|
||||
end
|
||||
|
||||
def test_check_ssl_virtual
|
||||
config = {
|
||||
:ServerName => "localhost",
|
||||
:SSLEnable => true,
|
||||
:SSLCertName => "/CN=localhost",
|
||||
}
|
||||
TestWEBrick.start_httpserver(config){|server, addr, port, log|
|
||||
assert_raise ArgumentError do
|
||||
vhost = WEBrick::HTTPServer.new({:DoNotListen => true, :Logger => NoLog})
|
||||
server.virtual_host(vhost)
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,543 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "test/unit"
|
||||
require "net/http"
|
||||
require "webrick"
|
||||
require_relative "utils"
|
||||
|
||||
class TestWEBrickHTTPServer < Test::Unit::TestCase
|
||||
empty_log = Object.new
|
||||
def empty_log.<<(str)
|
||||
assert_equal('', str)
|
||||
self
|
||||
end
|
||||
NoLog = WEBrick::Log.new(empty_log, WEBrick::BasicLog::WARN)
|
||||
|
||||
def teardown
|
||||
WEBrick::Utils::TimeoutHandler.terminate
|
||||
super
|
||||
end
|
||||
|
||||
def test_mount
|
||||
httpd = WEBrick::HTTPServer.new(
|
||||
:Logger => NoLog,
|
||||
:DoNotListen=>true
|
||||
)
|
||||
httpd.mount("/", :Root)
|
||||
httpd.mount("/foo", :Foo)
|
||||
httpd.mount("/foo/bar", :Bar, :bar1)
|
||||
httpd.mount("/foo/bar/baz", :Baz, :baz1, :baz2)
|
||||
|
||||
serv, opts, script_name, path_info = httpd.search_servlet("/")
|
||||
assert_equal(:Root, serv)
|
||||
assert_equal([], opts)
|
||||
assert_equal("", script_name)
|
||||
assert_equal("/", path_info)
|
||||
|
||||
serv, opts, script_name, path_info = httpd.search_servlet("/sub")
|
||||
assert_equal(:Root, serv)
|
||||
assert_equal([], opts)
|
||||
assert_equal("", script_name)
|
||||
assert_equal("/sub", path_info)
|
||||
|
||||
serv, opts, script_name, path_info = httpd.search_servlet("/sub/")
|
||||
assert_equal(:Root, serv)
|
||||
assert_equal([], opts)
|
||||
assert_equal("", script_name)
|
||||
assert_equal("/sub/", path_info)
|
||||
|
||||
serv, opts, script_name, path_info = httpd.search_servlet("/foo")
|
||||
assert_equal(:Foo, serv)
|
||||
assert_equal([], opts)
|
||||
assert_equal("/foo", script_name)
|
||||
assert_equal("", path_info)
|
||||
|
||||
serv, opts, script_name, path_info = httpd.search_servlet("/foo/")
|
||||
assert_equal(:Foo, serv)
|
||||
assert_equal([], opts)
|
||||
assert_equal("/foo", script_name)
|
||||
assert_equal("/", path_info)
|
||||
|
||||
serv, opts, script_name, path_info = httpd.search_servlet("/foo/sub")
|
||||
assert_equal(:Foo, serv)
|
||||
assert_equal([], opts)
|
||||
assert_equal("/foo", script_name)
|
||||
assert_equal("/sub", path_info)
|
||||
|
||||
serv, opts, script_name, path_info = httpd.search_servlet("/foo/bar")
|
||||
assert_equal(:Bar, serv)
|
||||
assert_equal([:bar1], opts)
|
||||
assert_equal("/foo/bar", script_name)
|
||||
assert_equal("", path_info)
|
||||
|
||||
serv, opts, script_name, path_info = httpd.search_servlet("/foo/bar/baz")
|
||||
assert_equal(:Baz, serv)
|
||||
assert_equal([:baz1, :baz2], opts)
|
||||
assert_equal("/foo/bar/baz", script_name)
|
||||
assert_equal("", path_info)
|
||||
end
|
||||
|
||||
class Req
|
||||
attr_reader :port, :host
|
||||
def initialize(addr, port, host)
|
||||
@addr, @port, @host = addr, port, host
|
||||
end
|
||||
def addr
|
||||
[0,0,0,@addr]
|
||||
end
|
||||
end
|
||||
|
||||
def httpd(addr, port, host, ali)
|
||||
config ={
|
||||
:Logger => NoLog,
|
||||
:DoNotListen => true,
|
||||
:BindAddress => addr,
|
||||
:Port => port,
|
||||
:ServerName => host,
|
||||
:ServerAlias => ali,
|
||||
}
|
||||
return WEBrick::HTTPServer.new(config)
|
||||
end
|
||||
|
||||
def assert_eql?(v1, v2)
|
||||
assert_equal(v1.object_id, v2.object_id)
|
||||
end
|
||||
|
||||
def test_lookup_server
|
||||
addr1 = "192.168.100.1"
|
||||
addr2 = "192.168.100.2"
|
||||
addrz = "192.168.100.254"
|
||||
local = "127.0.0.1"
|
||||
port1 = 80
|
||||
port2 = 8080
|
||||
port3 = 10080
|
||||
portz = 32767
|
||||
name1 = "www.example.com"
|
||||
name2 = "www2.example.com"
|
||||
name3 = "www3.example.com"
|
||||
namea = "www.example.co.jp"
|
||||
nameb = "www.example.jp"
|
||||
namec = "www2.example.co.jp"
|
||||
named = "www2.example.jp"
|
||||
namez = "foobar.example.com"
|
||||
alias1 = [namea, nameb]
|
||||
alias2 = [namec, named]
|
||||
|
||||
host1 = httpd(nil, port1, name1, nil)
|
||||
hosts = [
|
||||
host2 = httpd(addr1, port1, name1, nil),
|
||||
host3 = httpd(addr1, port1, name2, alias1),
|
||||
host4 = httpd(addr1, port2, name1, nil),
|
||||
host5 = httpd(addr1, port2, name2, alias1),
|
||||
httpd(addr1, port2, name3, alias2),
|
||||
host7 = httpd(addr2, nil, name1, nil),
|
||||
host8 = httpd(addr2, nil, name2, alias1),
|
||||
httpd(addr2, nil, name3, alias2),
|
||||
host10 = httpd(local, nil, nil, nil),
|
||||
host11 = httpd(nil, port3, nil, nil),
|
||||
].sort_by{ rand }
|
||||
hosts.each{|h| host1.virtual_host(h) }
|
||||
|
||||
# connect to addr1
|
||||
assert_eql?(host2, host1.lookup_server(Req.new(addr1, port1, name1)))
|
||||
assert_eql?(host3, host1.lookup_server(Req.new(addr1, port1, name2)))
|
||||
assert_eql?(host3, host1.lookup_server(Req.new(addr1, port1, namea)))
|
||||
assert_eql?(host3, host1.lookup_server(Req.new(addr1, port1, nameb)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addr1, port1, namez)))
|
||||
assert_eql?(host4, host1.lookup_server(Req.new(addr1, port2, name1)))
|
||||
assert_eql?(host5, host1.lookup_server(Req.new(addr1, port2, name2)))
|
||||
assert_eql?(host5, host1.lookup_server(Req.new(addr1, port2, namea)))
|
||||
assert_eql?(host5, host1.lookup_server(Req.new(addr1, port2, nameb)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addr1, port2, namez)))
|
||||
assert_eql?(host11, host1.lookup_server(Req.new(addr1, port3, name1)))
|
||||
assert_eql?(host11, host1.lookup_server(Req.new(addr1, port3, name2)))
|
||||
assert_eql?(host11, host1.lookup_server(Req.new(addr1, port3, namea)))
|
||||
assert_eql?(host11, host1.lookup_server(Req.new(addr1, port3, nameb)))
|
||||
assert_eql?(host11, host1.lookup_server(Req.new(addr1, port3, namez)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addr1, portz, name1)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addr1, portz, name2)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addr1, portz, namea)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addr1, portz, nameb)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addr1, portz, namez)))
|
||||
|
||||
# connect to addr2
|
||||
assert_eql?(host7, host1.lookup_server(Req.new(addr2, port1, name1)))
|
||||
assert_eql?(host8, host1.lookup_server(Req.new(addr2, port1, name2)))
|
||||
assert_eql?(host8, host1.lookup_server(Req.new(addr2, port1, namea)))
|
||||
assert_eql?(host8, host1.lookup_server(Req.new(addr2, port1, nameb)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addr2, port1, namez)))
|
||||
assert_eql?(host7, host1.lookup_server(Req.new(addr2, port2, name1)))
|
||||
assert_eql?(host8, host1.lookup_server(Req.new(addr2, port2, name2)))
|
||||
assert_eql?(host8, host1.lookup_server(Req.new(addr2, port2, namea)))
|
||||
assert_eql?(host8, host1.lookup_server(Req.new(addr2, port2, nameb)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addr2, port2, namez)))
|
||||
assert_eql?(host7, host1.lookup_server(Req.new(addr2, port3, name1)))
|
||||
assert_eql?(host8, host1.lookup_server(Req.new(addr2, port3, name2)))
|
||||
assert_eql?(host8, host1.lookup_server(Req.new(addr2, port3, namea)))
|
||||
assert_eql?(host8, host1.lookup_server(Req.new(addr2, port3, nameb)))
|
||||
assert_eql?(host11, host1.lookup_server(Req.new(addr2, port3, namez)))
|
||||
assert_eql?(host7, host1.lookup_server(Req.new(addr2, portz, name1)))
|
||||
assert_eql?(host8, host1.lookup_server(Req.new(addr2, portz, name2)))
|
||||
assert_eql?(host8, host1.lookup_server(Req.new(addr2, portz, namea)))
|
||||
assert_eql?(host8, host1.lookup_server(Req.new(addr2, portz, nameb)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addr2, portz, namez)))
|
||||
|
||||
# connect to addrz
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addrz, port1, name1)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addrz, port1, name2)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addrz, port1, namea)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addrz, port1, nameb)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addrz, port1, namez)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addrz, port2, name1)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addrz, port2, name2)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addrz, port2, namea)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addrz, port2, nameb)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addrz, port2, namez)))
|
||||
assert_eql?(host11, host1.lookup_server(Req.new(addrz, port3, name1)))
|
||||
assert_eql?(host11, host1.lookup_server(Req.new(addrz, port3, name2)))
|
||||
assert_eql?(host11, host1.lookup_server(Req.new(addrz, port3, namea)))
|
||||
assert_eql?(host11, host1.lookup_server(Req.new(addrz, port3, nameb)))
|
||||
assert_eql?(host11, host1.lookup_server(Req.new(addrz, port3, namez)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addrz, portz, name1)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addrz, portz, name2)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addrz, portz, namea)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addrz, portz, nameb)))
|
||||
assert_eql?(nil, host1.lookup_server(Req.new(addrz, portz, namez)))
|
||||
|
||||
# connect to localhost
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, port1, name1)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, port1, name2)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, port1, namea)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, port1, nameb)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, port1, namez)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, port2, name1)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, port2, name2)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, port2, namea)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, port2, nameb)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, port2, namez)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, port3, name1)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, port3, name2)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, port3, namea)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, port3, nameb)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, port3, namez)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, portz, name1)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, portz, name2)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, portz, namea)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, portz, nameb)))
|
||||
assert_eql?(host10, host1.lookup_server(Req.new(local, portz, namez)))
|
||||
end
|
||||
|
||||
def test_callbacks
|
||||
accepted = started = stopped = 0
|
||||
requested0 = requested1 = 0
|
||||
config = {
|
||||
:ServerName => "localhost",
|
||||
:AcceptCallback => Proc.new{ accepted += 1 },
|
||||
:StartCallback => Proc.new{ started += 1 },
|
||||
:StopCallback => Proc.new{ stopped += 1 },
|
||||
:RequestCallback => Proc.new{|req, res| requested0 += 1 },
|
||||
}
|
||||
log_tester = lambda {|log, access_log|
|
||||
assert(log.find {|s| %r{ERROR `/' not found\.} =~ s })
|
||||
assert_equal([], log.reject {|s| %r{ERROR `/' not found\.} =~ s })
|
||||
}
|
||||
TestWEBrick.start_httpserver(config, log_tester){|server, addr, port, log|
|
||||
vhost_config = {
|
||||
:ServerName => "myhostname",
|
||||
:BindAddress => addr,
|
||||
:Port => port,
|
||||
:DoNotListen => true,
|
||||
:Logger => NoLog,
|
||||
:AccessLog => [],
|
||||
:RequestCallback => Proc.new{|req, res| requested1 += 1 },
|
||||
}
|
||||
server.virtual_host(WEBrick::HTTPServer.new(vhost_config))
|
||||
|
||||
Thread.pass while server.status != :Running
|
||||
sleep 1 if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # server.status behaves unexpectedly with --jit-wait
|
||||
assert_equal(1, started, log.call)
|
||||
assert_equal(0, stopped, log.call)
|
||||
assert_equal(0, accepted, log.call)
|
||||
|
||||
http = Net::HTTP.new(addr, port)
|
||||
req = Net::HTTP::Get.new("/")
|
||||
req["Host"] = "myhostname:#{port}"
|
||||
http.request(req){|res| assert_equal("404", res.code, log.call)}
|
||||
http.request(req){|res| assert_equal("404", res.code, log.call)}
|
||||
http.request(req){|res| assert_equal("404", res.code, log.call)}
|
||||
req["Host"] = "localhost:#{port}"
|
||||
http.request(req){|res| assert_equal("404", res.code, log.call)}
|
||||
http.request(req){|res| assert_equal("404", res.code, log.call)}
|
||||
http.request(req){|res| assert_equal("404", res.code, log.call)}
|
||||
assert_equal(6, accepted, log.call)
|
||||
assert_equal(3, requested0, log.call)
|
||||
assert_equal(3, requested1, log.call)
|
||||
}
|
||||
assert_equal(started, 1)
|
||||
assert_equal(stopped, 1)
|
||||
end
|
||||
|
||||
class CustomRequest < ::WEBrick::HTTPRequest; end
|
||||
class CustomResponse < ::WEBrick::HTTPResponse; end
|
||||
class CustomServer < ::WEBrick::HTTPServer
|
||||
def create_request(config)
|
||||
CustomRequest.new(config)
|
||||
end
|
||||
|
||||
def create_response(config)
|
||||
CustomResponse.new(config)
|
||||
end
|
||||
end
|
||||
|
||||
def test_custom_server_request_and_response
|
||||
config = { :ServerName => "localhost" }
|
||||
TestWEBrick.start_server(CustomServer, config){|server, addr, port, log|
|
||||
server.mount_proc("/", lambda {|req, res|
|
||||
assert_kind_of(CustomRequest, req)
|
||||
assert_kind_of(CustomResponse, res)
|
||||
res.body = "via custom response"
|
||||
})
|
||||
Thread.pass while server.status != :Running
|
||||
|
||||
Net::HTTP.start(addr, port) do |http|
|
||||
req = Net::HTTP::Get.new("/")
|
||||
http.request(req){|res|
|
||||
assert_equal("via custom response", res.body)
|
||||
}
|
||||
server.shutdown
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# This class is needed by test_response_io_with_chunked_set method
|
||||
class EventManagerForChunkedResponseTest
|
||||
def initialize
|
||||
@listeners = []
|
||||
end
|
||||
def add_listener( &block )
|
||||
@listeners << block
|
||||
end
|
||||
def raise_str_event( str )
|
||||
@listeners.each{ |e| e.call( :str, str ) }
|
||||
end
|
||||
def raise_close_event()
|
||||
@listeners.each{ |e| e.call( :cls ) }
|
||||
end
|
||||
end
|
||||
def test_response_io_with_chunked_set
|
||||
evt_man = EventManagerForChunkedResponseTest.new
|
||||
t = Thread.new do
|
||||
begin
|
||||
config = {
|
||||
:ServerName => "localhost"
|
||||
}
|
||||
TestWEBrick.start_httpserver(config) do |server, addr, port, log|
|
||||
body_strs = [ 'aaaaaa', 'bb', 'cccc' ]
|
||||
server.mount_proc( "/", ->( req, res ){
|
||||
# Test for setting chunked...
|
||||
res.chunked = true
|
||||
r,w = IO.pipe
|
||||
evt_man.add_listener do |type,str|
|
||||
type == :cls ? ( w.close ) : ( w << str )
|
||||
end
|
||||
res.body = r
|
||||
} )
|
||||
Thread.pass while server.status != :Running
|
||||
http = Net::HTTP.new(addr, port)
|
||||
req = Net::HTTP::Get.new("/")
|
||||
http.request(req) do |res|
|
||||
i = 0
|
||||
evt_man.raise_str_event( body_strs[i] )
|
||||
res.read_body do |s|
|
||||
assert_equal( body_strs[i], s )
|
||||
i += 1
|
||||
if i < body_strs.length
|
||||
evt_man.raise_str_event( body_strs[i] )
|
||||
else
|
||||
evt_man.raise_close_event()
|
||||
end
|
||||
end
|
||||
assert_equal( body_strs.length, i )
|
||||
end
|
||||
end
|
||||
rescue => err
|
||||
flunk( 'exception raised in thread: ' + err.to_s )
|
||||
end
|
||||
end
|
||||
if t.join( 3 ).nil?
|
||||
evt_man.raise_close_event()
|
||||
flunk( 'timeout' )
|
||||
if t.join( 1 ).nil?
|
||||
Thread.kill t
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_response_io_without_chunked_set
|
||||
config = {
|
||||
:ServerName => "localhost"
|
||||
}
|
||||
log_tester = lambda {|log, access_log|
|
||||
assert_equal(1, log.length)
|
||||
assert_match(/WARN Could not determine content-length of response body./, log[0])
|
||||
}
|
||||
TestWEBrick.start_httpserver(config, log_tester){|server, addr, port, log|
|
||||
server.mount_proc("/", lambda { |req, res|
|
||||
r,w = IO.pipe
|
||||
# Test for not setting chunked...
|
||||
# res.chunked = true
|
||||
res.body = r
|
||||
w << "foo"
|
||||
w.close
|
||||
})
|
||||
Thread.pass while server.status != :Running
|
||||
http = Net::HTTP.new(addr, port)
|
||||
req = Net::HTTP::Get.new("/")
|
||||
req['Connection'] = 'Keep-Alive'
|
||||
begin
|
||||
Timeout.timeout(2) do
|
||||
http.request(req){|res| assert_equal("foo", res.body) }
|
||||
end
|
||||
rescue Timeout::Error
|
||||
flunk('corrupted response')
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def test_request_handler_callback_is_deprecated
|
||||
requested = 0
|
||||
config = {
|
||||
:ServerName => "localhost",
|
||||
:RequestHandler => Proc.new{|req, res| requested += 1 },
|
||||
}
|
||||
log_tester = lambda {|log, access_log|
|
||||
assert_equal(2, log.length)
|
||||
assert_match(/WARN :RequestHandler is deprecated, please use :RequestCallback/, log[0])
|
||||
assert_match(%r{ERROR `/' not found\.}, log[1])
|
||||
}
|
||||
TestWEBrick.start_httpserver(config, log_tester){|server, addr, port, log|
|
||||
Thread.pass while server.status != :Running
|
||||
|
||||
http = Net::HTTP.new(addr, port)
|
||||
req = Net::HTTP::Get.new("/")
|
||||
req["Host"] = "localhost:#{port}"
|
||||
http.request(req){|res| assert_equal("404", res.code, log.call)}
|
||||
assert_match(%r{:RequestHandler is deprecated, please use :RequestCallback$}, log.call, log.call)
|
||||
}
|
||||
assert_equal(1, requested)
|
||||
end
|
||||
|
||||
def test_shutdown_with_busy_keepalive_connection
|
||||
requested = 0
|
||||
config = {
|
||||
:ServerName => "localhost",
|
||||
}
|
||||
TestWEBrick.start_httpserver(config){|server, addr, port, log|
|
||||
server.mount_proc("/", lambda {|req, res| res.body = "heffalump" })
|
||||
Thread.pass while server.status != :Running
|
||||
|
||||
Net::HTTP.start(addr, port) do |http|
|
||||
req = Net::HTTP::Get.new("/")
|
||||
http.request(req){|res| assert_equal('Keep-Alive', res['Connection'], log.call) }
|
||||
server.shutdown
|
||||
begin
|
||||
10.times {|n| http.request(req); requested += 1 }
|
||||
rescue
|
||||
# Errno::ECONNREFUSED or similar
|
||||
end
|
||||
end
|
||||
}
|
||||
assert_equal(0, requested, "Server responded to #{requested} requests after shutdown")
|
||||
end
|
||||
|
||||
def test_cntrl_in_path
|
||||
log_ary = []
|
||||
access_log_ary = []
|
||||
config = {
|
||||
:Port => 0,
|
||||
:BindAddress => '127.0.0.1',
|
||||
:Logger => WEBrick::Log.new(log_ary, WEBrick::BasicLog::WARN),
|
||||
:AccessLog => [[access_log_ary, '']],
|
||||
}
|
||||
s = WEBrick::HTTPServer.new(config)
|
||||
s.mount('/foo', WEBrick::HTTPServlet::FileHandler, __FILE__)
|
||||
th = Thread.new { s.start }
|
||||
addr = s.listeners[0].addr
|
||||
|
||||
http = Net::HTTP.new(addr[3], addr[1])
|
||||
req = Net::HTTP::Get.new('/notexist%0a/foo')
|
||||
http.request(req) { |res| assert_equal('404', res.code) }
|
||||
exp = %Q(ERROR `/notexist\\n/foo' not found.\n)
|
||||
assert_equal 1, log_ary.size
|
||||
assert_include log_ary[0], exp
|
||||
ensure
|
||||
s&.shutdown
|
||||
th&.join
|
||||
end
|
||||
|
||||
def test_gigantic_request_header
|
||||
log_tester = lambda {|log, access_log|
|
||||
assert_equal 1, log.size
|
||||
assert_include log[0], 'ERROR headers too large'
|
||||
}
|
||||
TestWEBrick.start_httpserver({}, log_tester){|server, addr, port, log|
|
||||
server.mount('/', WEBrick::HTTPServlet::FileHandler, __FILE__)
|
||||
TCPSocket.open(addr, port) do |c|
|
||||
c.write("GET / HTTP/1.0\r\n")
|
||||
junk = -"X-Junk: #{' ' * 1024}\r\n"
|
||||
assert_raise(Errno::ECONNRESET, Errno::EPIPE, Errno::EPROTOTYPE) do
|
||||
loop { c.write(junk) }
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def test_eof_in_chunk
|
||||
log_tester = lambda do |log, access_log|
|
||||
assert_equal 1, log.size
|
||||
assert_include log[0], 'ERROR bad chunk data size'
|
||||
end
|
||||
TestWEBrick.start_httpserver({}, log_tester){|server, addr, port, log|
|
||||
server.mount_proc('/', ->(req, res) { res.body = req.body })
|
||||
TCPSocket.open(addr, port) do |c|
|
||||
c.write("POST / HTTP/1.1\r\nHost: example.com\r\n" \
|
||||
"Transfer-Encoding: chunked\r\n\r\n5\r\na")
|
||||
c.shutdown(Socket::SHUT_WR) # trigger EOF in server
|
||||
res = c.read
|
||||
assert_match %r{\AHTTP/1\.1 400 }, res
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def test_big_chunks
|
||||
nr_out = 3
|
||||
buf = 'big' # 3 bytes is bigger than 2!
|
||||
config = { :InputBufferSize => 2 }.freeze
|
||||
total = 0
|
||||
all = ''
|
||||
TestWEBrick.start_httpserver(config){|server, addr, port, log|
|
||||
server.mount_proc('/', ->(req, res) {
|
||||
err = []
|
||||
ret = req.body do |chunk|
|
||||
n = chunk.bytesize
|
||||
n > config[:InputBufferSize] and err << "#{n} > :InputBufferSize"
|
||||
total += n
|
||||
all << chunk
|
||||
end
|
||||
ret.nil? or err << 'req.body should return nil'
|
||||
(buf * nr_out) == all or err << 'input body does not match expected'
|
||||
res.header['connection'] = 'close'
|
||||
res.body = err.join("\n")
|
||||
})
|
||||
TCPSocket.open(addr, port) do |c|
|
||||
c.write("POST / HTTP/1.1\r\nHost: example.com\r\n" \
|
||||
"Transfer-Encoding: chunked\r\n\r\n")
|
||||
chunk = "#{buf.bytesize.to_s(16)}\r\n#{buf}\r\n"
|
||||
nr_out.times { c.write(chunk) }
|
||||
c.write("0\r\n\r\n")
|
||||
head, body = c.read.split("\r\n\r\n")
|
||||
assert_match %r{\AHTTP/1\.1 200 OK}, head
|
||||
assert_nil body
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,35 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "test/unit"
|
||||
require "webrick"
|
||||
|
||||
class TestWEBrickHTTPStatus < Test::Unit::TestCase
|
||||
def test_info?
|
||||
assert WEBrick::HTTPStatus.info?(100)
|
||||
refute WEBrick::HTTPStatus.info?(200)
|
||||
end
|
||||
|
||||
def test_success?
|
||||
assert WEBrick::HTTPStatus.success?(200)
|
||||
refute WEBrick::HTTPStatus.success?(300)
|
||||
end
|
||||
|
||||
def test_redirect?
|
||||
assert WEBrick::HTTPStatus.redirect?(300)
|
||||
refute WEBrick::HTTPStatus.redirect?(400)
|
||||
end
|
||||
|
||||
def test_error?
|
||||
assert WEBrick::HTTPStatus.error?(400)
|
||||
refute WEBrick::HTTPStatus.error?(600)
|
||||
end
|
||||
|
||||
def test_client_error?
|
||||
assert WEBrick::HTTPStatus.client_error?(400)
|
||||
refute WEBrick::HTTPStatus.client_error?(500)
|
||||
end
|
||||
|
||||
def test_server_error?
|
||||
assert WEBrick::HTTPStatus.server_error?(500)
|
||||
refute WEBrick::HTTPStatus.server_error?(600)
|
||||
end
|
||||
end
|
|
@ -1,101 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "test/unit"
|
||||
require "webrick/httputils"
|
||||
|
||||
class TestWEBrickHTTPUtils < Test::Unit::TestCase
|
||||
include WEBrick::HTTPUtils
|
||||
|
||||
def test_normilize_path
|
||||
assert_equal("/foo", normalize_path("/foo"))
|
||||
assert_equal("/foo/bar/", normalize_path("/foo/bar/"))
|
||||
|
||||
assert_equal("/", normalize_path("/foo/../"))
|
||||
assert_equal("/", normalize_path("/foo/.."))
|
||||
assert_equal("/", normalize_path("/foo/bar/../../"))
|
||||
assert_equal("/", normalize_path("/foo/bar/../.."))
|
||||
assert_equal("/", normalize_path("/foo/bar/../.."))
|
||||
assert_equal("/baz", normalize_path("/foo/bar/../../baz"))
|
||||
assert_equal("/baz", normalize_path("/foo/../bar/../baz"))
|
||||
assert_equal("/baz/", normalize_path("/foo/../bar/../baz/"))
|
||||
assert_equal("/...", normalize_path("/bar/../..."))
|
||||
assert_equal("/.../", normalize_path("/bar/../.../"))
|
||||
|
||||
assert_equal("/foo/", normalize_path("/foo/./"))
|
||||
assert_equal("/foo/", normalize_path("/foo/."))
|
||||
assert_equal("/foo/", normalize_path("/foo/././"))
|
||||
assert_equal("/foo/", normalize_path("/foo/./."))
|
||||
assert_equal("/foo/bar", normalize_path("/foo/./bar"))
|
||||
assert_equal("/foo/bar/", normalize_path("/foo/./bar/."))
|
||||
assert_equal("/foo/bar/", normalize_path("/./././foo/./bar/."))
|
||||
|
||||
assert_equal("/foo/bar/", normalize_path("//foo///.//bar/.///.//"))
|
||||
assert_equal("/", normalize_path("//foo///..///bar/.///..//.//"))
|
||||
|
||||
assert_raise(RuntimeError){ normalize_path("foo/bar") }
|
||||
assert_raise(RuntimeError){ normalize_path("..") }
|
||||
assert_raise(RuntimeError){ normalize_path("/..") }
|
||||
assert_raise(RuntimeError){ normalize_path("/./..") }
|
||||
assert_raise(RuntimeError){ normalize_path("/./../") }
|
||||
assert_raise(RuntimeError){ normalize_path("/./../..") }
|
||||
assert_raise(RuntimeError){ normalize_path("/./../../") }
|
||||
assert_raise(RuntimeError){ normalize_path("/./../") }
|
||||
assert_raise(RuntimeError){ normalize_path("/../..") }
|
||||
assert_raise(RuntimeError){ normalize_path("/../../") }
|
||||
assert_raise(RuntimeError){ normalize_path("/../../..") }
|
||||
assert_raise(RuntimeError){ normalize_path("/../../../") }
|
||||
assert_raise(RuntimeError){ normalize_path("/../foo/../") }
|
||||
assert_raise(RuntimeError){ normalize_path("/../foo/../../") }
|
||||
assert_raise(RuntimeError){ normalize_path("/foo/bar/../../../../") }
|
||||
assert_raise(RuntimeError){ normalize_path("/foo/../bar/../../") }
|
||||
assert_raise(RuntimeError){ normalize_path("/./../bar/") }
|
||||
assert_raise(RuntimeError){ normalize_path("/./../") }
|
||||
end
|
||||
|
||||
def test_split_header_value
|
||||
assert_equal(['foo', 'bar'], split_header_value('foo, bar'))
|
||||
assert_equal(['"foo"', 'bar'], split_header_value('"foo", bar'))
|
||||
assert_equal(['foo', '"bar"'], split_header_value('foo, "bar"'))
|
||||
assert_equal(['*'], split_header_value('*'))
|
||||
assert_equal(['W/"xyzzy"', 'W/"r2d2xxxx"', 'W/"c3piozzzz"'],
|
||||
split_header_value('W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"'))
|
||||
end
|
||||
|
||||
def test_escape
|
||||
assert_equal("/foo/bar", escape("/foo/bar"))
|
||||
assert_equal("/~foo/bar", escape("/~foo/bar"))
|
||||
assert_equal("/~foo%20bar", escape("/~foo bar"))
|
||||
assert_equal("/~foo%20bar", escape("/~foo bar"))
|
||||
assert_equal("/~foo%09bar", escape("/~foo\tbar"))
|
||||
assert_equal("/~foo+bar", escape("/~foo+bar"))
|
||||
bug8425 = '[Bug #8425] [ruby-core:55052]'
|
||||
assert_nothing_raised(ArgumentError, Encoding::CompatibilityError, bug8425) {
|
||||
assert_equal("%E3%83%AB%E3%83%93%E3%83%BC%E3%81%95%E3%82%93", escape("\u{30EB 30D3 30FC 3055 3093}"))
|
||||
}
|
||||
end
|
||||
|
||||
def test_escape_form
|
||||
assert_equal("%2Ffoo%2Fbar", escape_form("/foo/bar"))
|
||||
assert_equal("%2F~foo%2Fbar", escape_form("/~foo/bar"))
|
||||
assert_equal("%2F~foo+bar", escape_form("/~foo bar"))
|
||||
assert_equal("%2F~foo+%2B+bar", escape_form("/~foo + bar"))
|
||||
end
|
||||
|
||||
def test_unescape
|
||||
assert_equal("/foo/bar", unescape("%2ffoo%2fbar"))
|
||||
assert_equal("/~foo/bar", unescape("/%7efoo/bar"))
|
||||
assert_equal("/~foo/bar", unescape("%2f%7efoo%2fbar"))
|
||||
assert_equal("/~foo+bar", unescape("/%7efoo+bar"))
|
||||
end
|
||||
|
||||
def test_unescape_form
|
||||
assert_equal("//foo/bar", unescape_form("/%2Ffoo/bar"))
|
||||
assert_equal("//foo/bar baz", unescape_form("/%2Ffoo/bar+baz"))
|
||||
assert_equal("/~foo/bar baz", unescape_form("/%7Efoo/bar+baz"))
|
||||
end
|
||||
|
||||
def test_escape_path
|
||||
assert_equal("/foo/bar", escape_path("/foo/bar"))
|
||||
assert_equal("/foo/bar/", escape_path("/foo/bar/"))
|
||||
assert_equal("/%25foo/bar/", escape_path("/%foo/bar/"))
|
||||
end
|
||||
end
|
|
@ -1,41 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "test/unit"
|
||||
require "webrick/httpversion"
|
||||
|
||||
class TestWEBrickHTTPVersion < Test::Unit::TestCase
|
||||
def setup
|
||||
@v09 = WEBrick::HTTPVersion.new("0.9")
|
||||
@v10 = WEBrick::HTTPVersion.new("1.0")
|
||||
@v11 = WEBrick::HTTPVersion.new("1.001")
|
||||
end
|
||||
|
||||
def test_to_s()
|
||||
assert_equal("0.9", @v09.to_s)
|
||||
assert_equal("1.0", @v10.to_s)
|
||||
assert_equal("1.1", @v11.to_s)
|
||||
end
|
||||
|
||||
def test_major()
|
||||
assert_equal(0, @v09.major)
|
||||
assert_equal(1, @v10.major)
|
||||
assert_equal(1, @v11.major)
|
||||
end
|
||||
|
||||
def test_minor()
|
||||
assert_equal(9, @v09.minor)
|
||||
assert_equal(0, @v10.minor)
|
||||
assert_equal(1, @v11.minor)
|
||||
end
|
||||
|
||||
def test_compar()
|
||||
assert_equal(0, @v09 <=> "0.9")
|
||||
assert_equal(0, @v09 <=> "0.09")
|
||||
|
||||
assert_equal(-1, @v09 <=> @v10)
|
||||
assert_equal(-1, @v09 <=> "1.00")
|
||||
|
||||
assert_equal(1, @v11 <=> @v09)
|
||||
assert_equal(1, @v11 <=> "1.0")
|
||||
assert_equal(1, @v11 <=> "0.9")
|
||||
end
|
||||
end
|
|
@ -1,191 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "test/unit"
|
||||
require "tempfile"
|
||||
require "webrick"
|
||||
require_relative "utils"
|
||||
|
||||
class TestWEBrickServer < Test::Unit::TestCase
|
||||
class Echo < WEBrick::GenericServer
|
||||
def run(sock)
|
||||
while line = sock.gets
|
||||
sock << line
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_server
|
||||
TestWEBrick.start_server(Echo){|server, addr, port, log|
|
||||
TCPSocket.open(addr, port){|sock|
|
||||
sock.puts("foo"); assert_equal("foo\n", sock.gets, log.call)
|
||||
sock.puts("bar"); assert_equal("bar\n", sock.gets, log.call)
|
||||
sock.puts("baz"); assert_equal("baz\n", sock.gets, log.call)
|
||||
sock.puts("qux"); assert_equal("qux\n", sock.gets, log.call)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def test_start_exception
|
||||
stopped = 0
|
||||
|
||||
log = []
|
||||
logger = WEBrick::Log.new(log, WEBrick::BasicLog::WARN)
|
||||
|
||||
assert_raise(SignalException) do
|
||||
listener = Object.new
|
||||
def listener.to_io # IO.select invokes #to_io.
|
||||
raise SignalException, 'SIGTERM' # simulate signal in main thread
|
||||
end
|
||||
def listener.shutdown
|
||||
end
|
||||
def listener.close
|
||||
end
|
||||
|
||||
server = WEBrick::HTTPServer.new({
|
||||
:BindAddress => "127.0.0.1", :Port => 0,
|
||||
:StopCallback => Proc.new{ stopped += 1 },
|
||||
:Logger => logger,
|
||||
})
|
||||
server.listeners[0].close
|
||||
server.listeners[0] = listener
|
||||
|
||||
server.start
|
||||
end
|
||||
|
||||
assert_equal(1, stopped)
|
||||
assert_equal(1, log.length)
|
||||
assert_match(/FATAL SignalException: SIGTERM/, log[0])
|
||||
end
|
||||
|
||||
def test_callbacks
|
||||
accepted = started = stopped = 0
|
||||
config = {
|
||||
:AcceptCallback => Proc.new{ accepted += 1 },
|
||||
:StartCallback => Proc.new{ started += 1 },
|
||||
:StopCallback => Proc.new{ stopped += 1 },
|
||||
}
|
||||
TestWEBrick.start_server(Echo, config){|server, addr, port, log|
|
||||
true while server.status != :Running
|
||||
sleep 1 if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # server.status behaves unexpectedly with --jit-wait
|
||||
assert_equal(1, started, log.call)
|
||||
assert_equal(0, stopped, log.call)
|
||||
assert_equal(0, accepted, log.call)
|
||||
TCPSocket.open(addr, port){|sock| (sock << "foo\n").gets }
|
||||
TCPSocket.open(addr, port){|sock| (sock << "foo\n").gets }
|
||||
TCPSocket.open(addr, port){|sock| (sock << "foo\n").gets }
|
||||
assert_equal(3, accepted, log.call)
|
||||
}
|
||||
assert_equal(1, started)
|
||||
assert_equal(1, stopped)
|
||||
end
|
||||
|
||||
def test_daemon
|
||||
begin
|
||||
r, w = IO.pipe
|
||||
pid1 = Process.fork{
|
||||
r.close
|
||||
WEBrick::Daemon.start
|
||||
w.puts(Process.pid)
|
||||
sleep 10
|
||||
}
|
||||
pid2 = r.gets.to_i
|
||||
assert(Process.kill(:KILL, pid2))
|
||||
assert_not_equal(pid1, pid2)
|
||||
rescue NotImplementedError
|
||||
# snip this test
|
||||
ensure
|
||||
Process.wait(pid1) if pid1
|
||||
r.close
|
||||
w.close
|
||||
end
|
||||
end
|
||||
|
||||
def test_restart_after_shutdown
|
||||
address = '127.0.0.1'
|
||||
port = 0
|
||||
log = []
|
||||
config = {
|
||||
:BindAddress => address,
|
||||
:Port => port,
|
||||
:Logger => WEBrick::Log.new(log, WEBrick::BasicLog::WARN),
|
||||
}
|
||||
server = Echo.new(config)
|
||||
client_proc = lambda {|str|
|
||||
begin
|
||||
ret = server.listeners.first.connect_address.connect {|s|
|
||||
s.write(str)
|
||||
s.close_write
|
||||
s.read
|
||||
}
|
||||
assert_equal(str, ret)
|
||||
ensure
|
||||
server.shutdown
|
||||
end
|
||||
}
|
||||
server_thread = Thread.new { server.start }
|
||||
client_thread = Thread.new { client_proc.call("a") }
|
||||
assert_join_threads([client_thread, server_thread])
|
||||
server.listen(address, port)
|
||||
server_thread = Thread.new { server.start }
|
||||
client_thread = Thread.new { client_proc.call("b") }
|
||||
assert_join_threads([client_thread, server_thread])
|
||||
assert_equal([], log)
|
||||
end
|
||||
|
||||
def test_restart_after_stop
|
||||
log = Object.new
|
||||
class << log
|
||||
include Test::Unit::Assertions
|
||||
def <<(msg)
|
||||
flunk "unexpected log: #{msg.inspect}"
|
||||
end
|
||||
end
|
||||
client_thread = nil
|
||||
wakeup = -> {client_thread.wakeup}
|
||||
warn_flunk = WEBrick::Log.new(log, WEBrick::BasicLog::WARN)
|
||||
server = WEBrick::HTTPServer.new(
|
||||
:StartCallback => wakeup,
|
||||
:StopCallback => wakeup,
|
||||
:BindAddress => '0.0.0.0',
|
||||
:Port => 0,
|
||||
:Logger => warn_flunk)
|
||||
2.times {
|
||||
server_thread = Thread.start {
|
||||
server.start
|
||||
}
|
||||
client_thread = Thread.start {
|
||||
sleep 0.1 until server.status == :Running || !server_thread.status
|
||||
server.stop
|
||||
sleep 0.1 until server.status == :Stop || !server_thread.status
|
||||
}
|
||||
assert_join_threads([client_thread, server_thread])
|
||||
}
|
||||
end
|
||||
|
||||
def test_port_numbers
|
||||
config = {
|
||||
:BindAddress => '0.0.0.0',
|
||||
:Logger => WEBrick::Log.new([], WEBrick::BasicLog::WARN),
|
||||
}
|
||||
|
||||
ports = [0, "0"]
|
||||
|
||||
ports.each do |port|
|
||||
config[:Port]= port
|
||||
server = WEBrick::GenericServer.new(config)
|
||||
server_thread = Thread.start { server.start }
|
||||
client_thread = Thread.start {
|
||||
sleep 0.1 until server.status == :Running || !server_thread.status
|
||||
server_port = server.listeners[0].addr[1]
|
||||
server.stop
|
||||
assert_equal server.config[:Port], server_port
|
||||
sleep 0.1 until server.status == :Stop || !server_thread.status
|
||||
}
|
||||
assert_join_threads([client_thread, server_thread])
|
||||
end
|
||||
|
||||
assert_raise(ArgumentError) do
|
||||
config[:Port]= "FOO"
|
||||
WEBrick::GenericServer.new(config)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,67 +0,0 @@
|
|||
require "test/unit"
|
||||
require "webrick"
|
||||
require "webrick/ssl"
|
||||
require_relative "utils"
|
||||
require 'timeout'
|
||||
|
||||
class TestWEBrickSSLServer < Test::Unit::TestCase
|
||||
class Echo < WEBrick::GenericServer
|
||||
def run(sock)
|
||||
while line = sock.gets
|
||||
sock << line
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_self_signed_cert_server
|
||||
assert_self_signed_cert(
|
||||
:SSLEnable => true,
|
||||
:SSLCertName => [["C", "JP"], ["O", "www.ruby-lang.org"], ["CN", "Ruby"]],
|
||||
)
|
||||
end
|
||||
|
||||
def test_self_signed_cert_server_with_string
|
||||
assert_self_signed_cert(
|
||||
:SSLEnable => true,
|
||||
:SSLCertName => "/C=JP/O=www.ruby-lang.org/CN=Ruby",
|
||||
)
|
||||
end
|
||||
|
||||
def assert_self_signed_cert(config)
|
||||
TestWEBrick.start_server(Echo, config){|server, addr, port, log|
|
||||
io = TCPSocket.new(addr, port)
|
||||
sock = OpenSSL::SSL::SSLSocket.new(io)
|
||||
sock.connect
|
||||
sock.puts(server.ssl_context.cert.subject.to_s)
|
||||
assert_equal("/C=JP/O=www.ruby-lang.org/CN=Ruby\n", sock.gets, log.call)
|
||||
sock.close
|
||||
io.close
|
||||
}
|
||||
end
|
||||
|
||||
def test_slow_connect
|
||||
poke = lambda do |io, msg|
|
||||
begin
|
||||
sock = OpenSSL::SSL::SSLSocket.new(io)
|
||||
sock.connect
|
||||
sock.puts(msg)
|
||||
assert_equal "#{msg}\n", sock.gets, msg
|
||||
ensure
|
||||
sock&.close
|
||||
io.close
|
||||
end
|
||||
end
|
||||
config = {
|
||||
:SSLEnable => true,
|
||||
:SSLCertName => "/C=JP/O=www.ruby-lang.org/CN=Ruby",
|
||||
}
|
||||
EnvUtil.timeout(10) do
|
||||
TestWEBrick.start_server(Echo, config) do |server, addr, port, log|
|
||||
outer = TCPSocket.new(addr, port)
|
||||
inner = TCPSocket.new(addr, port)
|
||||
poke.call(inner, 'fast TLS negotiation')
|
||||
poke.call(outer, 'slow TLS negotiation')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,110 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "test/unit"
|
||||
require "webrick/utils"
|
||||
|
||||
class TestWEBrickUtils < Test::Unit::TestCase
|
||||
def teardown
|
||||
WEBrick::Utils::TimeoutHandler.terminate
|
||||
super
|
||||
end
|
||||
|
||||
def assert_expired(m)
|
||||
Thread.handle_interrupt(Timeout::Error => :never, EX => :never) do
|
||||
assert_empty(m::TimeoutHandler.instance.instance_variable_get(:@timeout_info))
|
||||
end
|
||||
end
|
||||
|
||||
def assert_not_expired(m)
|
||||
Thread.handle_interrupt(Timeout::Error => :never, EX => :never) do
|
||||
assert_not_empty(m::TimeoutHandler.instance.instance_variable_get(:@timeout_info))
|
||||
end
|
||||
end
|
||||
|
||||
EX = Class.new(StandardError)
|
||||
|
||||
def test_no_timeout
|
||||
m = WEBrick::Utils
|
||||
assert_equal(:foo, m.timeout(10){ :foo })
|
||||
assert_expired(m)
|
||||
end
|
||||
|
||||
def test_nested_timeout_outer
|
||||
m = WEBrick::Utils
|
||||
i = 0
|
||||
assert_raise(Timeout::Error){
|
||||
m.timeout(1){
|
||||
assert_raise(Timeout::Error){ m.timeout(0.1){ i += 1; sleep(1) } }
|
||||
assert_not_expired(m)
|
||||
i += 1
|
||||
sleep(2)
|
||||
}
|
||||
}
|
||||
assert_equal(2, i)
|
||||
assert_expired(m)
|
||||
end
|
||||
|
||||
def test_timeout_default_exception
|
||||
m = WEBrick::Utils
|
||||
assert_raise(Timeout::Error){ m.timeout(0.01){ sleep } }
|
||||
assert_expired(m)
|
||||
end
|
||||
|
||||
def test_timeout_custom_exception
|
||||
m = WEBrick::Utils
|
||||
ex = EX
|
||||
assert_raise(ex){ m.timeout(0.01, ex){ sleep } }
|
||||
assert_expired(m)
|
||||
end
|
||||
|
||||
def test_nested_timeout_inner_custom_exception
|
||||
m = WEBrick::Utils
|
||||
ex = EX
|
||||
i = 0
|
||||
assert_raise(ex){
|
||||
m.timeout(10){
|
||||
m.timeout(0.01, ex){ i += 1; sleep }
|
||||
}
|
||||
sleep
|
||||
}
|
||||
assert_equal(1, i)
|
||||
assert_expired(m)
|
||||
end
|
||||
|
||||
def test_nested_timeout_outer_custom_exception
|
||||
m = WEBrick::Utils
|
||||
ex = EX
|
||||
i = 0
|
||||
assert_raise(Timeout::Error){
|
||||
m.timeout(0.01){
|
||||
m.timeout(1.0, ex){ i += 1; sleep }
|
||||
}
|
||||
sleep
|
||||
}
|
||||
assert_equal(1, i)
|
||||
assert_expired(m)
|
||||
end
|
||||
|
||||
def test_create_listeners
|
||||
addr = listener_address(0)
|
||||
port = addr.slice!(1)
|
||||
assert_kind_of(Integer, port, "dynamically chosen port number")
|
||||
assert_equal(["AF_INET", "127.0.0.1", "127.0.0.1"], addr)
|
||||
|
||||
assert_equal(["AF_INET", port, "127.0.0.1", "127.0.0.1"],
|
||||
listener_address(port),
|
||||
"specific port number")
|
||||
|
||||
assert_equal(["AF_INET", port, "127.0.0.1", "127.0.0.1"],
|
||||
listener_address(port.to_s),
|
||||
"specific port number string")
|
||||
end
|
||||
|
||||
def listener_address(port)
|
||||
listeners = WEBrick::Utils.create_listeners("127.0.0.1", port)
|
||||
srv = listeners.first
|
||||
assert_kind_of TCPServer, srv
|
||||
srv.addr
|
||||
ensure
|
||||
listeners.each(&:close) if listeners
|
||||
end
|
||||
end
|
|
@ -1,104 +0,0 @@
|
|||
# frozen_string_literal: false
|
||||
require "webrick"
|
||||
begin
|
||||
require "webrick/https"
|
||||
rescue LoadError
|
||||
end
|
||||
require "webrick/httpproxy"
|
||||
|
||||
module TestWEBrick
|
||||
NullWriter = Object.new
|
||||
def NullWriter.<<(msg)
|
||||
puts msg if $DEBUG
|
||||
return self
|
||||
end
|
||||
|
||||
class WEBrick::HTTPServlet::CGIHandler
|
||||
remove_const :Ruby
|
||||
require "envutil" unless defined?(EnvUtil)
|
||||
Ruby = EnvUtil.rubybin
|
||||
remove_const :CGIRunner
|
||||
CGIRunner = "\"#{Ruby}\" \"#{WEBrick::Config::LIBDIR}/httpservlet/cgi_runner.rb\"" # :nodoc:
|
||||
remove_const :CGIRunnerArray
|
||||
CGIRunnerArray = [Ruby, "#{WEBrick::Config::LIBDIR}/httpservlet/cgi_runner.rb"] # :nodoc:
|
||||
end
|
||||
|
||||
RubyBin = "\"#{EnvUtil.rubybin}\""
|
||||
RubyBin << " --disable-gems"
|
||||
RubyBin << " \"-I#{File.expand_path("../..", File.dirname(__FILE__))}/lib\""
|
||||
RubyBin << " \"-I#{File.dirname(EnvUtil.rubybin)}/.ext/common\""
|
||||
RubyBin << " \"-I#{File.dirname(EnvUtil.rubybin)}/.ext/#{RUBY_PLATFORM}\""
|
||||
|
||||
RubyBinArray = [EnvUtil.rubybin]
|
||||
RubyBinArray << "--disable-gems"
|
||||
RubyBinArray << "-I" << "#{File.expand_path("../..", File.dirname(__FILE__))}/lib"
|
||||
RubyBinArray << "-I" << "#{File.dirname(EnvUtil.rubybin)}/.ext/common"
|
||||
RubyBinArray << "-I" << "#{File.dirname(EnvUtil.rubybin)}/.ext/#{RUBY_PLATFORM}"
|
||||
|
||||
require "test/unit" unless defined?(Test::Unit)
|
||||
include Test::Unit::Assertions
|
||||
extend Test::Unit::Assertions
|
||||
include Test::Unit::CoreAssertions
|
||||
extend Test::Unit::CoreAssertions
|
||||
|
||||
module_function
|
||||
|
||||
DefaultLogTester = lambda {|log, access_log| assert_equal([], log) }
|
||||
|
||||
def start_server(klass, config={}, log_tester=DefaultLogTester, &block)
|
||||
log_ary = []
|
||||
access_log_ary = []
|
||||
log = proc { "webrick log start:\n" + (log_ary+access_log_ary).join.gsub(/^/, " ").chomp + "\nwebrick log end" }
|
||||
config = ({
|
||||
:BindAddress => "127.0.0.1", :Port => 0,
|
||||
:ServerType => Thread,
|
||||
:Logger => WEBrick::Log.new(log_ary, WEBrick::BasicLog::WARN),
|
||||
:AccessLog => [[access_log_ary, ""]]
|
||||
}.update(config))
|
||||
server = capture_output {break klass.new(config)}
|
||||
server_thread = server.start
|
||||
server_thread2 = Thread.new {
|
||||
server_thread.join
|
||||
if log_tester
|
||||
log_tester.call(log_ary, access_log_ary)
|
||||
end
|
||||
}
|
||||
addr = server.listeners[0].addr
|
||||
client_thread = Thread.new {
|
||||
begin
|
||||
block.yield([server, addr[3], addr[1], log])
|
||||
ensure
|
||||
server.shutdown
|
||||
end
|
||||
}
|
||||
assert_join_threads([client_thread, server_thread2])
|
||||
end
|
||||
|
||||
def start_httpserver(config={}, log_tester=DefaultLogTester, &block)
|
||||
start_server(WEBrick::HTTPServer, config, log_tester, &block)
|
||||
end
|
||||
|
||||
def start_httpproxy(config={}, log_tester=DefaultLogTester, &block)
|
||||
start_server(WEBrick::HTTPProxyServer, config, log_tester, &block)
|
||||
end
|
||||
|
||||
def start_cgi_server(config={}, log_tester=TestWEBrick::DefaultLogTester, &block)
|
||||
config = {
|
||||
:CGIInterpreter => TestWEBrick::RubyBin,
|
||||
:DocumentRoot => File.dirname(__FILE__),
|
||||
:DirectoryIndex => ["webrick.cgi"],
|
||||
:RequestCallback => Proc.new{|req, res|
|
||||
def req.meta_vars
|
||||
meta = super
|
||||
meta["RUBYLIB"] = $:.join(File::PATH_SEPARATOR)
|
||||
meta[RbConfig::CONFIG['LIBPATHENV']] = ENV[RbConfig::CONFIG['LIBPATHENV']] if RbConfig::CONFIG['LIBPATHENV']
|
||||
return meta
|
||||
end
|
||||
},
|
||||
}.merge(config)
|
||||
if RUBY_PLATFORM =~ /mswin|mingw|cygwin|bccwin32/
|
||||
config[:CGIPathEnv] = ENV['PATH'] # runtime dll may not be in system dir.
|
||||
end
|
||||
start_server(WEBrick::HTTPServer, config, log_tester, &block)
|
||||
end
|
||||
end
|
|
@ -1,38 +0,0 @@
|
|||
#!ruby
|
||||
require "webrick/cgi"
|
||||
|
||||
class TestApp < WEBrick::CGI
|
||||
def do_GET(req, res)
|
||||
res["content-type"] = "text/plain"
|
||||
if req.path_info == "/dumpenv"
|
||||
res.body = Marshal.dump(ENV.to_hash)
|
||||
elsif (p = req.path_info) && p.length > 0
|
||||
res.body = p
|
||||
elsif (q = req.query).size > 0
|
||||
res.body = q.keys.sort.collect{|key|
|
||||
q[key].list.sort.collect{|v|
|
||||
"#{key}=#{v}"
|
||||
}.join(", ")
|
||||
}.join(", ")
|
||||
elsif %r{/$} =~ req.request_uri.to_s
|
||||
res.body = +""
|
||||
res.body << req.request_uri.to_s << "\n"
|
||||
res.body << req.script_name
|
||||
elsif !req.cookies.empty?
|
||||
res.body = req.cookies.inject(+""){|result, cookie|
|
||||
result << "%s=%s\n" % [cookie.name, cookie.value]
|
||||
}
|
||||
res.cookies << WEBrick::Cookie.new("Customer", "WILE_E_COYOTE")
|
||||
res.cookies << WEBrick::Cookie.new("Shipping", "FedEx")
|
||||
else
|
||||
res.body = req.script_name
|
||||
end
|
||||
end
|
||||
|
||||
def do_POST(req, res)
|
||||
do_GET(req, res)
|
||||
end
|
||||
end
|
||||
|
||||
cgi = TestApp.new
|
||||
cgi.start
|
|
@ -1,4 +0,0 @@
|
|||
req to <%=
|
||||
servlet_request.request_uri
|
||||
%> <%=
|
||||
servlet_request.query.inspect %>
|
|
@ -1,36 +0,0 @@
|
|||
#!ruby
|
||||
require "webrick/cgi"
|
||||
|
||||
class TestApp < WEBrick::CGI
|
||||
def do_GET(req, res)
|
||||
res["content-type"] = "text/plain"
|
||||
if (p = req.path_info) && p.length > 0
|
||||
res.body = p
|
||||
elsif (q = req.query).size > 0
|
||||
res.body = q.keys.sort.collect{|key|
|
||||
q[key].list.sort.collect{|v|
|
||||
"#{key}=#{v}"
|
||||
}.join(", ")
|
||||
}.join(", ")
|
||||
elsif %r{/$} =~ req.request_uri.to_s
|
||||
res.body = ""
|
||||
res.body << req.request_uri.to_s << "\n"
|
||||
res.body << req.script_name
|
||||
elsif !req.cookies.empty?
|
||||
res.body = req.cookies.inject(""){|result, cookie|
|
||||
result << "%s=%s\n" % [cookie.name, cookie.value]
|
||||
}
|
||||
res.cookies << WEBrick::Cookie.new("Customer", "WILE_E_COYOTE")
|
||||
res.cookies << WEBrick::Cookie.new("Shipping", "FedEx")
|
||||
else
|
||||
res.body = req.script_name
|
||||
end
|
||||
end
|
||||
|
||||
def do_POST(req, res)
|
||||
do_GET(req, res)
|
||||
end
|
||||
end
|
||||
|
||||
cgi = TestApp.new
|
||||
cgi.start
|
Загрузка…
Ссылка в новой задаче