pax_global_header00006660000000000000000000000064147232450140014514gustar00rootroot0000000000000052 comment=307f24c0e9624e56fdbe8ebbe6df03ee25e9e57b webrick-1.9.1/000077500000000000000000000000001472324501400131525ustar00rootroot00000000000000webrick-1.9.1/.github/000077500000000000000000000000001472324501400145125ustar00rootroot00000000000000webrick-1.9.1/.github/dependabot.yml000066400000000000000000000001661472324501400173450ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: 'github-actions' directory: '/' schedule: interval: 'weekly' webrick-1.9.1/.github/workflows/000077500000000000000000000000001472324501400165475ustar00rootroot00000000000000webrick-1.9.1/.github/workflows/push_gem.yml000066400000000000000000000022101472324501400210740ustar00rootroot00000000000000name: Publish gem to rubygems.org on: push: tags: - 'v*' permissions: contents: read jobs: push: if: github.repository == 'ruby/webrick' runs-on: ubuntu-latest environment: name: rubygems.org url: https://rubygems.org/gems/webrick permissions: contents: write id-token: write steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Set up Ruby uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0 with: bundler-cache: true ruby-version: ruby - name: Publish to RubyGems uses: rubygems/release-gem@9e85cb11501bebc2ae661c1500176316d3987059 # v1.1.0 - name: Create GitHub release run: | tag_name="$(git describe --tags --abbrev=0)" gh release create "${tag_name}" --verify-tag --generate-notes env: GITHUB_TOKEN: ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }} webrick-1.9.1/.github/workflows/test.yml000066400000000000000000000023061472324501400202520ustar00rootroot00000000000000name: test on: [push, pull_request] jobs: ruby-versions: uses: ruby/actions/.github/workflows/ruby_versions.yml@master with: engine: cruby min_version: 2.4 test: needs: ruby-versions name: build (${{ matrix.ruby }} / ${{ matrix.os }}) strategy: fail-fast: false matrix: ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} os: [ ubuntu-latest, macos-latest ] # CRuby < 2.6 does not support macos-arm64, so test those on amd64 instead exclude: - { os: macos-latest, ruby: '2.4' } - { os: macos-latest, ruby: '2.5' } include: - { os: macos-13, ruby: '2.4' } - { os: macos-13, ruby: '2.5' } runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true # 'bundle install' and cache - name: Run test run: bundle exec rake test - name: RBS validate run: bundle exec rbs -r openssl -r digest -r uri -r erb -r singleton -r tempfile -r socket -I sig validate if: ${{ ! startsWith(matrix.ruby, '2.') }} # rbs requires ruby 3.0+ webrick-1.9.1/.gitignore000066400000000000000000000001271472324501400151420ustar00rootroot00000000000000/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ webrick-1.9.1/Gemfile000066400000000000000000000002641472324501400144470ustar00rootroot00000000000000source 'https://rubygems.org' gemspec gem "rake" gem "test-unit" gem "test-unit-ruby-core" # rbs requires ruby 3.0+ gem "rbs", require: false if !RUBY_VERSION.start_with?('2.') webrick-1.9.1/LICENSE.txt000066400000000000000000000024021472324501400147730ustar00rootroot00000000000000Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. webrick-1.9.1/README.md000066400000000000000000000045141472324501400144350ustar00rootroot00000000000000# Webrick 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. WEBrick is suitable for use in testing and for development. However, while the developers of WEBrick will attempt to fix security issues, they do not encourage the use of WEBrick to serve production web applications that may be subject to hostile input. ## Installation Add this line to your application's Gemfile: ```ruby gem 'webrick' ``` And then execute: $ bundle Or install it yourself as: $ gem install webrick ## Usage 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: ```ruby 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: ```ruby trap 'INT' do server.shutdown end server.start ``` ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/webrick. ## License The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause). webrick-1.9.1/Rakefile000066400000000000000000000003241472324501400146160ustar00rootroot00000000000000require "bundler/gem_tasks" require "rake/testtask" Rake::TestTask.new(:test) do |t| t.libs << "test/lib" t.ruby_opts << "-rhelper" t.test_files = FileList["test/**/test_*.rb"] end task :default => :test webrick-1.9.1/bin/000077500000000000000000000000001472324501400137225ustar00rootroot00000000000000webrick-1.9.1/bin/console000077500000000000000000000005261472324501400153150ustar00rootroot00000000000000#!/usr/bin/env ruby require "bundler/setup" require "webrick" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start require "irb" IRB.start(__FILE__) webrick-1.9.1/bin/setup000077500000000000000000000002031472324501400150030ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' set -vx bundle install # Do any other automated setup that you need to do here webrick-1.9.1/lib/000077500000000000000000000000001472324501400137205ustar00rootroot00000000000000webrick-1.9.1/lib/webrick.rb000066400000000000000000000155411472324501400157010ustar00rootroot00000000000000# 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' webrick-1.9.1/lib/webrick/000077500000000000000000000000001472324501400153465ustar00rootroot00000000000000webrick-1.9.1/lib/webrick/accesslog.rb000066400000000000000000000105231472324501400176370ustar00rootroot00000000000000# frozen_string_literal: true #-- # 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 req.attributes # %{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 webrick-1.9.1/lib/webrick/cgi.rb000066400000000000000000000200251472324501400164340ustar00rootroot00000000000000# frozen_string_literal: true # # 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 @options 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 webrick-1.9.1/lib/webrick/compat.rb000066400000000000000000000016561472324501400171660ustar00rootroot00000000000000# frozen_string_literal: true # # 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 webrick-1.9.1/lib/webrick/config.rb000066400000000000000000000133111472324501400171370ustar00rootroot00000000000000# frozen_string_literal: true # # 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 webrick-1.9.1/lib/webrick/cookie.rb000066400000000000000000000076461472324501400171610ustar00rootroot00000000000000# frozen_string_literal: true # # 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 webrick-1.9.1/lib/webrick/htmlutils.rb000066400000000000000000000013061472324501400177200ustar00rootroot00000000000000# frozen_string_literal: true #-- # 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!(/ '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 webrick-1.9.1/lib/webrick/httpauth/000077500000000000000000000000001472324501400172075ustar00rootroot00000000000000webrick-1.9.1/lib/webrick/httpauth/authenticator.rb000066400000000000000000000060601472324501400224100ustar00rootroot00000000000000# frozen_string_literal: true #-- # 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: ResponseInfoField = "Proxy-Authentication-Info" # :nodoc: AuthException = HTTPStatus::ProxyAuthenticationRequired # :nodoc: end end end webrick-1.9.1/lib/webrick/httpauth/basicauth.rb000066400000000000000000000063771472324501400215140ustar00rootroot00000000000000# frozen_string_literal: true # # 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 webrick-1.9.1/lib/webrick/httpauth/digestauth.rb000066400000000000000000000314561472324501400217060ustar00rootroot00000000000000# frozen_string_literal: true # # 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 webrick-1.9.1/lib/webrick/httpauth/htdigest.rb000066400000000000000000000066721472324501400213620ustar00rootroot00000000000000# frozen_string_literal: true # # 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 webrick-1.9.1/lib/webrick/httpauth/htgroup.rb000066400000000000000000000046731472324501400212360ustar00rootroot00000000000000# frozen_string_literal: true # # 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*/, 2) @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 webrick-1.9.1/lib/webrick/httpauth/htpasswd.rb000066400000000000000000000111401472324501400213660ustar00rootroot00000000000000# frozen_string_literal: true # # 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(":", 2) 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(":", 2) 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 webrick-1.9.1/lib/webrick/httpauth/userdb.rb000066400000000000000000000025011472324501400210160ustar00rootroot00000000000000# frozen_string_literal: true #-- # 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 webrick-1.9.1/lib/webrick/httpproxy.rb000066400000000000000000000245561472324501400177700ustar00rootroot00000000000000# frozen_string_literal: true # # 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 webrick-1.9.1/lib/webrick/httprequest.rb000066400000000000000000000415611472324501400202720ustar00rootroot00000000000000# frozen_string_literal: true # # 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 @raw_header = Array.new @header = nil @cookies = [] @accept = [] @accept_charset = [] @accept_encoding = [] @accept_language = [] @body = +"" @addr = [] @peeraddr = [] @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++)(?: 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 end_of_headers = false while line = read_line(socket) if line == CRLF end_of_headers = true break end if (@request_bytes += line.bytesize) > MAX_HEADER_LENGTH raise HTTPStatus::RequestEntityTooLarge, 'headers too large' end if line.include?("\x00") raise HTTPStatus::BadRequest, 'null byte in header' end @raw_header << line end # Allow if @header already set to support chunked trailers raise HTTPStatus::EOFError unless end_of_headers || @header end @header = HTTPUtils::parse_header(@raw_header.join) if (content_length = @header['content-length']) && content_length.length != 0 if content_length.length > 1 raise HTTPStatus::BadRequest, "multiple content-length request headers" elsif !/\A\d+\z/.match?(content_length[0]) raise HTTPStatus::BadRequest, "invalid content-length request header" end end 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"] host, port = parse_host_request_line(self["host"]) 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 host_pattern = URI::RFC2396_Parser.new.pattern.fetch(:HOST) HOST_PATTERN = /\A(#{host_pattern})(?::(\d+))?\z/n def parse_host_request_line(host) host.scan(HOST_PATTERN)[0] end def read_body(socket, block) return unless socket if tc = self['transfer-encoding'] if self['content-length'] raise HTTPStatus::BadRequest, "request with both transfer-encoding and content-length, possible request smuggling" end 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) raise HTTPStatus::LengthRequired end return @body end def read_chunk_size(socket) line = read_line(socket) if /\A([0-9a-fA-F]+)(?:;(\S+(?:=\S+)?))?\r\n\z/ =~ 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 line = read_line(socket) # skip CRLF unless line == "\r\n" raise HTTPStatus::BadRequest, "extra data after chunk '#{line}'." end 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 webrick-1.9.1/lib/webrick/httpresponse.rb000066400000000000000000000333611472324501400204370ustar00rootroot00000000000000# frozen_string_literal: true # # 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 header['content-length'] 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 ## # Set the response body proc as an streaming/upgrade response. attr_accessor :upgrade ## # 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 ## # Sets the response to be a streaming/upgrade response. # This will disable keep-alive and chunked transfer encoding. def upgrade!(protocol) @upgrade = protocol @keep_alive = false @chunked = false 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 if @upgrade @header['connection'] = 'upgrade' @header['upgrade'] = @upgrade @keep_alive = false return end # 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?(:bytesize) @header['content-length'] = @body.bytesize.to_s else @header['connection'] = 'close' 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().dup @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 = "#{url}.\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_ #{HTMLUtils::escape(@reason_phrase)}

#{HTMLUtils::escape(@reason_phrase)}

#{HTMLUtils::escape(ex.message)}
_end_of_html_ if backtrace && $DEBUG @body << "backtrace of '#{HTMLUtils::escape(ex.class.to_s)}' " @body << "#{HTMLUtils::escape(ex.message)}" @body << "
"
        ex.backtrace.each{|line| @body << "\t#{line}\n"}
        @body << "

" end @body << <<-_end_of_html_
#{HTMLUtils::escape(@config[:ServerSoftware])} at #{host}:#{port}
_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 if @bodytempfile @bodytempfile.rewind IO.copy_stream(@bodytempfile, socket) else @body.call(socket) end if content_length = @header['content-length'] @sent_size = content_length.to_i end 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 webrick-1.9.1/lib/webrick/https.rb000066400000000000000000000061141472324501400170370ustar00rootroot00000000000000# frozen_string_literal: true # # 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 webrick-1.9.1/lib/webrick/httpserver.rb000066400000000000000000000202571472324501400201070ustar00rootroot00000000000000# frozen_string_literal: true # # 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 'io/wait' 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 webrick-1.9.1/lib/webrick/httpservlet.rb000066400000000000000000000013001472324501400202510ustar00rootroot00000000000000# frozen_string_literal: true # # 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 webrick-1.9.1/lib/webrick/httpservlet/000077500000000000000000000000001472324501400177325ustar00rootroot00000000000000webrick-1.9.1/lib/webrick/httpservlet/abstract.rb000066400000000000000000000103071472324501400220630ustar00rootroot00000000000000# frozen_string_literal: true # # 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 = "

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 webrick-1.9.1/lib/webrick/httpservlet/cgi_runner.rb000066400000000000000000000017671472324501400224250ustar00rootroot00000000000000# frozen_string_literal: true # # 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"] webrick-1.9.1/lib/webrick/httpservlet/cgihandler.rb000066400000000000000000000075151472324501400223670ustar00rootroot00000000000000# frozen_string_literal: true # # 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 webrick-1.9.1/lib/webrick/httpservlet/erbhandler.rb000066400000000000000000000044301472324501400223660ustar00rootroot00000000000000# frozen_string_literal: true # # 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 webrick-1.9.1/lib/webrick/httpservlet/filehandler.rb000066400000000000000000000424701472324501400225430ustar00rootroot00000000000000# frozen_string_literal: true # # 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}")}.dup 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_ #{title}

#{title}

_end_of_html_ res.body << "\n" res.body << "" res.body << "" res.body << "\n" res.body << "\n" res.body << "\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 = +"" s << "" s << "\n" res.body << s } res.body << "
NameLast modifiedSize
#{HTMLUtils::escape(dname)}" << (time ? time.strftime("%Y/%m/%d %H:%M") : "") << "" << (size >= 0 ? size.to_s : "-") << "
" res.body << "
" res.body << <<-_end_of_html_
#{HTMLUtils::escape(@config[:ServerSoftware])}
at #{req.host}:#{req.port}
_end_of_html_ end # :startdoc: end end end webrick-1.9.1/lib/webrick/httpservlet/prochandler.rb000066400000000000000000000020661472324501400225640ustar00rootroot00000000000000# frozen_string_literal: true # # 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 alias do_PUT do_GET # :startdoc: end end end webrick-1.9.1/lib/webrick/httpstatus.rb000066400000000000000000000123641472324501400201240ustar00rootroot00000000000000# frozen_string_literal: true #-- # 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 webrick-1.9.1/lib/webrick/httputils.rb000066400000000000000000000333501472324501400177370ustar00rootroot00000000000000# frozen_string_literal: true # # 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", "avif" => "image/avif", "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", "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", "ico" => "image/x-icon", "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", "mp4" => "video/mp4", "mpe" => "video/mpeg", "mpeg" => "video/mpeg", "mpg" => "video/mpeg", "otf" => "font/otf", "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", "ttc" => "font/collection", "ttf" => "font/ttf", "txt" => "text/plain", "wasm" => "application/wasm", "webm" => "video/webm", "webmanifest" => "application/manifest+json", "webp" => "image/webp", "woff" => "font/woff", "woff2" => "font/woff2", "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. class SplitHeader < Array def join(separator = ", ") super end end class CookieHeader < Array def join(separator = "; ") super end end HEADER_CLASSES = Hash.new(SplitHeader).update({ "cookie" => CookieHeader, }) def parse_header(raw) header = Hash.new([].freeze) field = nil raw.each_line{|line| case line when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):([^\r\n\0]*?)\r\n\z/om field, value = $1, $2 field.downcase! header[field] = HEADER_CLASSES[field].new unless header.has_key?(field) header[field] << value when /^[ \t]+([^\r\n\0]*?)\r\n/om unless field raise HTTPStatus::BadRequest, "bad header '#{line}'." end value = line value.gsub!(/\A[ \t]+/, '') value.slice!(-2..-1) header[field][-1] << " " << value else raise HTTPStatus::BadRequest, "bad header '#{line}'." end } header.each{|key, values| values.each{|value| value.gsub!(/\A[ \t]+/, '') value.gsub!(/[ \t]+\z/, '') } } header end module_function :parse_header ## # Splits a header value +str+ according to HTTP specification. def split_header_value(str) str.scan(%r'\G((?:"(?:\\.|[^"])+?"|[^",]++)+) (?:,[ \t]*|\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(/,[ \t]*/) parts.each {|part| if m = %r{^([^ \t,]+?)(?:;[ \t]*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 /[ \t]+name="(.*?)"/ =~ cd then @name = $1 end if /[ \t]+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 webrick-1.9.1/lib/webrick/httpversion.rb000066400000000000000000000031511472324501400202600ustar00rootroot00000000000000# frozen_string_literal: true #-- # 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 webrick-1.9.1/lib/webrick/log.rb000066400000000000000000000077631472324501400164710ustar00rootroot00000000000000# frozen_string_literal: true #-- # 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 "[%Y-%m-%d %H:%M:%S]" 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 webrick-1.9.1/lib/webrick/server.rb000066400000000000000000000243131472324501400172040ustar00rootroot00000000000000# frozen_string_literal: true # # 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 # :MaxClients 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 be 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 :WEBrickSocket 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:
" 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:
" 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 webrick-1.9.1/lib/webrick/ssl.rb000066400000000000000000000165621472324501400165060ustar00rootroot00000000000000# frozen_string_literal: true # # 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 # :SSLCertName 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 = if $VERBOSE 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 } else OpenSSL::PKey::RSA.new(bits) 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 webrick-1.9.1/lib/webrick/utils.rb000066400000000000000000000155501472324501400170410ustar00rootroot00000000000000# frozen_string_literal: true # # 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 webrick-1.9.1/lib/webrick/version.rb000066400000000000000000000006361472324501400173650ustar00rootroot00000000000000# frozen_string_literal: true #-- # 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.9.1" end webrick-1.9.1/sig/000077500000000000000000000000001472324501400137345ustar00rootroot00000000000000webrick-1.9.1/sig/accesslog.rbs000066400000000000000000000010061472324501400164040ustar00rootroot00000000000000module WEBrick module AccessLog class AccessLogError < StandardError end CLF_TIME_FORMAT: String COMMON_LOG_FORMAT: String CLF: String REFERER_LOG_FORMAT: String AGENT_LOG_FORMAT: String COMBINED_LOG_FORMAT: String def self?.setup_params: (Hash[Symbol, untyped] config, HTTPRequest req, HTTPResponse res) -> Hash[String, untyped] def self?.format: (String format_string, Hash[String, untyped] params) -> String def self?.escape: (String data) -> String end end webrick-1.9.1/sig/cgi.rbs000066400000000000000000000034231472324501400152100ustar00rootroot00000000000000module WEBrick class CGI @options: Array[untyped] class CGIError < StandardError end attr_reader config: Hash[Symbol, untyped] attr_reader logger: BasicLog def initialize: (*untyped args) -> void def []: (Symbol key) -> untyped interface _Env def []: (String) -> String? end def start: (?_Env env, ?IO stdin, ?IO stdout) -> void def self.setup_header: () -> untyped def self.status_line: () -> "" def service: (HTTPRequest req, HTTPResponse res) -> void class Socket @config: Hash[Symbol, untyped] @env: _Env @header_part: StringIO @body_part: IO @out_port: IO @server_addr: String @server_name: String? @server_port: String? @remote_addr: String? @remote_host: String? @remote_port: (String | 0) include Enumerable[String] private def initialize: (Hash[Symbol, untyped] config, _Env env, IO stdin, IO stdout) -> void def request_line: () -> String def setup_header: () -> void def add_header: (String hdrname, String value) -> void def input: () -> (IO | StringIO) public def peeraddr: () -> [nil, (String | 0), String?, String?] def addr: () -> [nil, String?, String?, String] def gets: (?String eol, ?Integer? size) -> String? def read: (?Integer? size) -> String? def each: () { (String) -> void } -> void def eof?: () -> bool def <<: (_ToS data) -> IO def write: (_ToS data) -> Integer def cert: () -> OpenSSL::X509::Certificate? def peer_cert: () -> OpenSSL::X509::Certificate? def peer_cert_chain: () -> Array[OpenSSL::X509::Certificate]? def cipher: () -> [String?, String?, String?, String?]? end end end webrick-1.9.1/sig/compat.rbs000066400000000000000000000006641472324501400157350ustar00rootroot00000000000000# # 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 class EPROTO < SystemCallError end class ECONNRESET < SystemCallError end class ECONNABORTED < SystemCallError end end webrick-1.9.1/sig/config.rbs000066400000000000000000000005021472324501400157060ustar00rootroot00000000000000module WEBrick module Config LIBDIR: String # for GenericServer General: Hash[Symbol, untyped] # for HTTPServer, HTTPRequest, HTTPResponse ... HTTP: Hash[Symbol, untyped] FileHandler: Hash[Symbol, untyped] BasicAuth: Hash[Symbol, untyped] DigestAuth: Hash[Symbol, untyped] end end webrick-1.9.1/sig/cookie.rbs000066400000000000000000000013061472324501400157150ustar00rootroot00000000000000module WEBrick class Cookie @expires: String? attr_reader name: String? attr_accessor value: String? attr_accessor version: Integer # # The cookie domain attr_accessor domain: String? attr_accessor path: String? attr_accessor secure: true? attr_accessor comment: String? attr_accessor max_age: Integer? def initialize: (untyped name, untyped value) -> void def expires=: ((Time | _ToS)? t) -> untyped def expires: () -> Time? def to_s: () -> String def self.parse: (String? str) -> Array[instance]? def self.parse_set_cookie: (String str) -> instance def self.parse_set_cookies: (String str) -> Array[instance] end end webrick-1.9.1/sig/htmlutils.rbs000066400000000000000000000001351472324501400164700ustar00rootroot00000000000000module WEBrick module HTMLUtils def self?.escape: (String? string) -> String end end webrick-1.9.1/sig/httpauth.rbs000066400000000000000000000010431472324501400163030ustar00rootroot00000000000000module WEBrick module HTTPAuth interface _Callable def call: (String user, String pass) -> bool end def self?._basic_auth: (HTTPRequest req, HTTPResponse res, String realm, String req_field, String res_field, HTTPStatus::Error err_type, _Callable block) -> void def self?.basic_auth: (HTTPRequest req, HTTPResponse res, String realm) { (String user, String pass) -> bool } -> void def self?.proxy_basic_auth: (HTTPRequest req, HTTPResponse res, String realm) { (String user, String pass) -> bool } -> void end end webrick-1.9.1/sig/httpauth/000077500000000000000000000000001472324501400155755ustar00rootroot00000000000000webrick-1.9.1/sig/httpauth/authenticator.rbs000066400000000000000000000020231472324501400211540ustar00rootroot00000000000000module WEBrick module HTTPAuth module Authenticator @reload_db: bool? @request_field: String @response_field: String @resp_info_field: String @auth_exception: singleton(HTTPStatus::ClientError) @auth_scheme: String RequestField: String ResponseField: String ResponseInfoField: String AuthException: singleton(HTTPStatus::ClientError) AuthScheme: String? attr_reader realm: String? attr_reader userdb: UserDB attr_reader logger: Log private def check_init: (Hash[Symbol, untyped] config) -> void def check_scheme: (HTTPRequest req) -> String? def log: (interned meth, String fmt, *untyped args) -> void def error: (String fmt, *untyped args) -> void def info: (String fmt, *untyped args) -> void end module ProxyAuthenticator RequestField: String ResponseField: String ResponseInfoField: String AuthException: singleton(HTTPStatus::ClientError) end end end webrick-1.9.1/sig/httpauth/basicauth.rbs000066400000000000000000000012141472324501400202460ustar00rootroot00000000000000module WEBrick module HTTPAuth class BasicAuth @config: Hash[Symbol, untyped] include Authenticator AuthScheme: String def self.make_passwd: (String? realm, String? user, String? pass) -> String attr_reader realm: String? attr_reader userdb: UserDB attr_reader logger: Log def initialize: (Hash[Symbol, untyped] config, ?Hash[Symbol, untyped] default) -> void def authenticate: (HTTPRequest req, HTTPResponse res) -> void def challenge: (HTTPRequest req, HTTPResponse res) -> bot end class ProxyBasicAuth < BasicAuth include ProxyAuthenticator end end end webrick-1.9.1/sig/httpauth/digestauth.rbs000066400000000000000000000037051472324501400204530ustar00rootroot00000000000000module WEBrick module HTTPAuth class DigestAuth @config: Hash[Symbol, untyped] @domain: Array[String]? @use_opaque: bool @use_next_nonce: bool @check_nc: bool @use_auth_info_header: bool @nonce_expire_period: Integer @nonce_expire_delta: Integer @internet_explorer_hack: bool @h: singleton(Digest::Base) @instance_key: String @opaques: Hash[String, OpaqueInfo] @last_nonce_expire: Time @mutex: Thread::Mutex include Authenticator AuthScheme: String class OpaqueInfo < Struct[untyped] attr_accessor time(): Time attr_accessor nonce(): String? attr_accessor nc(): String end attr_reader algorithm: String? attr_reader qop: Array[String] def self.make_passwd: (String realm, String user, String pass) -> untyped def initialize: (Hash[Symbol, untyped] config, ?Hash[Symbol, untyped] default) -> void def authenticate: (HTTPRequest req, HTTPResponse res) -> void def challenge: (HTTPRequest req, HTTPResponse res, ?bool stale) -> bot private MustParams: Array[String] MustParamsAuth: Array[String] def _authenticate: (HTTPRequest req, HTTPResponse res) -> (:nonce_is_stale | bool) def split_param_value: (String string) -> Hash[String, String] def generate_next_nonce: (HTTPRequest req) -> String def check_nonce: (HTTPRequest req, Hash[String, String] auth_req) -> bool def generate_opaque: (HTTPRequest req) -> String def check_opaque: (OpaqueInfo opaque_struct, untyped req, Hash[String, String] auth_req) -> bool def check_uri: (HTTPRequest req, Hash[String, String] auth_req) -> bool def hexdigest: (*_ToS? args) -> String end class ProxyDigestAuth < DigestAuth include ProxyAuthenticator private def check_uri: (HTTPRequest req, Hash[String, String] auth_req) -> true end end end webrick-1.9.1/sig/httpauth/htdigest.rbs000066400000000000000000000012301472324501400201140ustar00rootroot00000000000000module WEBrick module HTTPAuth class Htdigest @path: String @mtime: Time @digest: Hash[String, Hash[String, String]] @mutex: Thread::Mutex @auth_type: String include UserDB def initialize: (String path) -> void def reload: () -> void def flush: (?String? output) -> void def get_passwd: (String realm, String user, bool reload_db) -> String? def set_passwd: (String realm, String user, String pass) -> String def delete_passwd: (String realm, String user) -> String? def each: () { (String user, String realm, String password_hash) -> void } -> void end end end webrick-1.9.1/sig/httpauth/htgroup.rbs000066400000000000000000000006011472324501400177720ustar00rootroot00000000000000module WEBrick module HTTPAuth class Htgroup @path: String @mtime: Time @group: Hash[String, Array[String]] def initialize: (String path) -> void def reload: () -> void def flush: (?String? output) -> void def members: (String group) -> Array[String] def add: (String group, Array[String] members) -> void end end end webrick-1.9.1/sig/httpauth/htpasswd.rbs000066400000000000000000000012321472324501400201400ustar00rootroot00000000000000module WEBrick module HTTPAuth class Htpasswd @path: String @mtime: Time @passwd: Hash[String, String] @auth_type: String @password_hash: (:crypt | :bcrypt) include UserDB def initialize: (String path, ?password_hash: (:crypt | :bcrypt)?) -> void def reload: () -> void def flush: (?String? output) -> void def get_passwd: (String realm, String user, bool reload_db) -> String? def set_passwd: (String realm, String user, String pass) -> void def delete_passwd: (String realm, String user) -> String def each: () { ([String, String]) -> void } -> void end end end webrick-1.9.1/sig/httpauth/userdb.rbs000066400000000000000000000005141472324501400175710ustar00rootroot00000000000000module WEBrick module HTTPAuth module UserDB attr_accessor auth_type: String def make_passwd: (String realm, String user, String pass) -> String def set_passwd: (String realm, String user, String pass) -> void def get_passwd: (String realm, String user, ?bool reload_db) -> String end end end webrick-1.9.1/sig/httpproxy.rbs000066400000000000000000000036541472324501400165350ustar00rootroot00000000000000module WEBrick NullReader: untyped def self.read: (*untyped args) -> nil alias self.gets self.read FakeProxyURI: untyped def self.method_missing: (untyped meth, *untyped args) -> (nil | untyped) class HTTPProxyServer < HTTPServer @via: untyped def initialize: (?::Hash[untyped, untyped] config, ?untyped default) -> void # :stopdoc: def service: (HTTPRequest req, HTTPResponse res) -> untyped def proxy_auth: (HTTPRequest req, HTTPResponse res) -> untyped def proxy_uri: (HTTPRequest req, HTTPResponse res) -> untyped def proxy_service: (HTTPRequest req, HTTPResponse res) -> untyped def do_CONNECT: (HTTPRequest req, HTTPResponse res) -> untyped def do_GET: (HTTPRequest req, HTTPResponse res) -> untyped def do_HEAD: (HTTPRequest req, HTTPResponse res) -> untyped def do_POST: (HTTPRequest req, HTTPResponse res) -> untyped def do_OPTIONS: (HTTPRequest req, HTTPResponse res) -> untyped private # Some header fields should not be transferred. HopByHop: ::Array["connection" | "keep-alive" | "proxy-authenticate" | "upgrade" | "proxy-authorization" | "te" | "trailers" | "transfer-encoding"] ShouldNotTransfer: ::Array["set-cookie" | "proxy-connection"] def split_field: (untyped f) -> (untyped | ::Array[untyped]) def choose_header: (untyped src, untyped dst) -> untyped # Net::HTTP is stupid about the multiple header fields. # Here is workaround: def set_cookie: (untyped src, untyped dst) -> (untyped | nil) def set_via: (untyped h) -> (untyped | nil) def setup_proxy_header: (HTTPRequest req, HTTPResponse res) -> untyped def setup_upstream_proxy_authentication: (HTTPRequest req, HTTPResponse res, untyped header) -> untyped def create_net_http: (untyped uri, untyped upstream) -> untyped def perform_proxy_request: (HTTPRequest req, HTTPResponse res, untyped req_class, ?untyped? body_stream) -> untyped end end webrick-1.9.1/sig/httprequest.rbs000066400000000000000000000071151472324501400170400ustar00rootroot00000000000000module WEBrick class HTTPRequest @config: Hash[Symbol, untyped] @buffer_size: Integer @logger: Log @query: Hash[String, HTTPUtils::FormData]? @body: String @remaining_size: Integer? @socket: TCPSocket? @forwarded_proto: String? @host: String? @port: Integer? @body_tmp: Array[String] @body_rd: Fiber @request_bytes: Integer @forwarded_server: String? @forwarded_host: String? @forwarded_port: Integer? @forwarded_for: String? BODY_CONTAINABLE_METHODS: Array[String] attr_reader request_line: String? attr_reader request_method: String? attr_reader unparsed_uri: String? attr_reader http_version: HTTPVersion? attr_reader request_uri: URI::Generic? attr_reader path: String? attr_accessor script_name: String? attr_accessor path_info: String? attr_accessor query_string: String? attr_reader raw_header: Array[String] attr_reader header: Hash[String, Array[String]]? attr_reader cookies: Array[Cookie] attr_reader accept: Array[String] attr_reader accept_charset: Array[String] attr_reader accept_encoding: Array[String] attr_reader accept_language: Array[String] attr_accessor user: String? attr_reader addr: ([String, Integer, String, String] | []) attr_reader peeraddr: ([String, Integer, String, String] | []) attr_reader attributes: Hash[untyped, untyped] attr_reader keep_alive: bool attr_reader request_time: Time? def initialize: (Hash[Symbol, untyped] config) -> void def parse: (?TCPSocket? socket) -> void def continue: () -> void type body_chunk_block = ^(String body_chunk) -> void def body: () ?{ (String body_chunk) -> void } -> String def body_reader: () -> self # for IO.copy_stream. def readpartial: (Integer size, ?String buf) -> String def query: () -> Hash[String, HTTPUtils::FormData] def content_length: () -> Integer def content_type: () -> String? def []: (String header_name) -> String? def each: [T] () { (String, String) -> T } -> T? def host: () -> String? def port: () -> Integer? def server_name: () -> String? def remote_ip: () -> String? def ssl?: () -> bool def keep_alive?: () -> bool def to_s: () -> String def fixup: () -> void def meta_vars: () -> Hash[String, String] private MAX_URI_LENGTH: Integer # same as Mongrel, Thin and Puma MAX_HEADER_LENGTH: Integer def read_request_line: (IO socket) -> void def read_header: (IO socket) -> void def parse_uri: (String str, ?String scheme) -> URI::Generic HOST_PATTERN: Regexp def parse_host_request_line: (String host) -> [String, String] def read_body: (IO socket, body_chunk_block block) -> String | (nil socket, top block) -> nil def read_chunk_size: (IO socket) -> [Integer, String?] def read_chunked: (IO socket, body_chunk_block block) -> void def _read_data: (IO io, Symbol method, *untyped arg) -> String? def read_line: (IO io, ?Integer size) -> String? def read_data: (IO io, Integer size) -> String? def parse_query: () -> void PrivateNetworkRegexp: Regexp # 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: () -> void end end webrick-1.9.1/sig/httpresponse.rbs000066400000000000000000000050241472324501400172030ustar00rootroot00000000000000module WEBrick class HTTPResponse @buffer_size: Integer @logger: Log @chunked: bool @bodytempfile: File | Tempfile | nil class InvalidHeader < StandardError end attr_reader http_version: HTTPVersion attr_reader status: Integer attr_reader header: Hash[String, String] attr_reader cookies: Array[Cookie] attr_accessor reason_phrase: String interface _CallableBody def call: (_Writer) -> void end attr_accessor body: String | _ReaderPartial | _CallableBody attr_accessor request_method: String? attr_accessor request_uri: URI::Generic? attr_accessor request_http_version: HTTPVersion? attr_accessor filename: String? attr_accessor keep_alive: bool attr_reader config: Hash[Symbol, untyped] attr_reader sent_size: Integer attr_accessor upgrade: String? def initialize: (Hash[Symbol, untyped] config) -> void def status_line: () -> String def status=: (Integer status) -> Integer def []: (String field) -> String? def []=: (String field, _ToS value) -> _ToS def content_length: () -> Integer? def content_length=: (Integer len) -> Integer def content_type: () -> String? def content_type=: (String type) -> String def each: () { (String, String) -> void } -> void def chunked?: () -> bool def chunked=: (boolish val) -> boolish def keep_alive?: () -> bool def upgrade!: (String protocol) -> void def send_response: (_Writer socket) -> void def setup_header: () -> void def make_body_tempfile: () -> void def remove_body_tempfile: () -> void def send_header: (_Writer socket) -> void def send_body: (_Writer socket) -> void def set_redirect: (singleton(WEBrick::HTTPStatus::Redirect) status, URI::Generic | String url) -> bot def set_error: (singleton(Exception) ex, ?bool backtrace) -> void private def check_header: (_ToS header_value) -> String def error_body: (bool backtrace, singleton(Exception) ex, String? host, Integer? port) -> void def send_body_io: (_Writer socket) -> void def send_body_string: (_Writer socket) -> void def send_body_proc: (_Writer socket) -> void class ChunkedWrapper @socket: _Writer @resp: HTTPResponse def initialize: (_Writer socket, HTTPResponse resp) -> void def write: (_ToS buf) -> Integer def <<: (*_ToS buf) -> self end # preserved for compatibility with some 3rd-party handlers def _write_data: [T] (T socket, _ToS data) -> T end end webrick-1.9.1/sig/https.rbs000066400000000000000000000022231472324501400156050ustar00rootroot00000000000000module WEBrick class HTTPRequest @client_cert_chain: Array[OpenSSL::X509::Certificate] attr_reader cipher: [String, String, Integer, Integer]? attr_reader server_cert: OpenSSL::X509::Certificate attr_reader client_cert: OpenSSL::X509::Certificate? alias orig_parse parse def parse: (?(TCPSocket | OpenSSL::SSL::SSLSocket)? socket) -> void | ... alias orig_parse_uri parse_uri private def parse_uri: (String str, ?::String scheme) -> URI::Generic | ... public alias orig_meta_vars meta_vars def meta_vars: () -> Hash[String, String] | ... end class SNIRequest attr_reader host: String? attr_reader addr: [String, Integer, String, String] attr_reader port: Integer def initialize: (OpenSSL::SSL::SSLSocket sslsocket, ?String? hostname) -> void end class HTTPServer < ::WEBrick::GenericServer def ssl_servername_callback: (OpenSSL::SSL::SSLSocket sslsocket, ?String? hostname) -> OpenSSL::SSL::SSLContext? alias orig_virtual_host virtual_host def virtual_host: (instance server) -> void | ... end end webrick-1.9.1/sig/httpserver.rbs000066400000000000000000000037721472324501400166630ustar00rootroot00000000000000module WEBrick class HTTPServerError < ServerError end class HTTPServer < ::WEBrick::GenericServer @http_version: HTTPVersion @mount_tab: MountTable @virtual_hosts: Array[untyped] def initialize: (?Hash[Symbol, untyped] config, ?Hash[Symbol, untyped] default) -> void def run: (TCPSocket sock) -> void def service: (HTTPRequest req, HTTPResponse res) -> void def do_OPTIONS: (HTTPRequest req, HTTPResponse res) -> void def mount: (String dir, singleton(HTTPServlet::AbstractServlet) servlet, *untyped options) -> void def mount_proc: (String dir, ?HTTPServlet::ProcHandler::_Callable proc) -> void | (String dir, ?nil proc) { (HTTPRequest, HTTPResponse) -> void } -> void def unmount: (String dir) -> MountTable::value_type alias umount unmount def search_servlet: (String path) -> [singleton(HTTPServlet::AbstractServlet), Array[untyped], String, String]? def virtual_host: (instance server) -> void def lookup_server: (HTTPRequest req) -> instance? def access_log: (Hash[Symbol, untyped] config, HTTPRequest req, HTTPResponse res) -> void # # Creates the HTTPRequest used when handling the HTTP # request. Can be overridden by subclasses. def create_request: (Hash[Symbol, untyped] with_webrick_config) -> HTTPRequest # # Creates the HTTPResponse used when handling the HTTP # request. Can be overridden by subclasses. def create_response: (Hash[Symbol, untyped] with_webrick_config) -> HTTPResponse class MountTable type value_type = [singleton(HTTPServlet::AbstractServlet), Array[untyped]] @tab: Hash[String, value_type] @scanner: Regexp def initialize: () -> void def []: (String dir) -> value_type def []=: (String dir, value_type val) -> value_type def delete: (String dir) -> value_type def scan: (String path) -> [String, String] private def compile: () -> void def normalize: (String dir) -> String end end end webrick-1.9.1/sig/httpservlet.rbs000066400000000000000000000000561472324501400170310ustar00rootroot00000000000000module WEBrick module HTTPServlet end end webrick-1.9.1/sig/httpservlet/000077500000000000000000000000001472324501400163205ustar00rootroot00000000000000webrick-1.9.1/sig/httpservlet/abstract.rbs000066400000000000000000000015271472324501400206400ustar00rootroot00000000000000module WEBrick module HTTPServlet class HTTPServletError < StandardError end class AbstractServlet @server: HTTPServer interface _Config def []: (Symbol) -> untyped end @config: _Config @logger: Log @options: untyped # Array[untyped] causes RBS::InstanceVariableTypeError def self.get_instance: (HTTPServer server, *untyped options) -> instance def initialize: (HTTPServer server, *untyped options) -> void def service: (HTTPRequest req, HTTPResponse res) -> void def do_GET: (HTTPRequest req, HTTPResponse res) -> bot def do_HEAD: (HTTPRequest req, HTTPResponse res) -> bot def do_OPTIONS: (HTTPRequest req, HTTPResponse res) -> void private def redirect_to_directory_uri: (HTTPRequest req, HTTPResponse res) -> void end end end webrick-1.9.1/sig/httpservlet/cgi_runner.rbs000066400000000000000000000001001472324501400211520ustar00rootroot00000000000000class Object def sysread: (IO io, Integer size) -> String end webrick-1.9.1/sig/httpservlet/cgihandler.rbs000066400000000000000000000006541472324501400211350ustar00rootroot00000000000000module WEBrick module HTTPServlet class CGIHandler < AbstractServlet @script_filename: String @tempdir: String? @cgicmd: Array[String] | String Ruby: String CGIRunner: String CGIRunnerArray: [String, String] def initialize: (HTTPServer server, String name) -> void def do_GET: (HTTPRequest req, HTTPResponse res) -> void alias do_POST do_GET end end end webrick-1.9.1/sig/httpservlet/erbhandler.rbs000066400000000000000000000006131472324501400211360ustar00rootroot00000000000000module WEBrick module HTTPServlet class ERBHandler < AbstractServlet @script_filename: String def initialize: (HTTPServer server, String name) -> void def do_GET: (HTTPRequest req, HTTPResponse res) -> void alias do_POST do_GET private def evaluate: (ERB erb, HTTPRequest servlet_request, HTTPResponse servlet_response) -> String end end end webrick-1.9.1/sig/httpservlet/filehandler.rbs000066400000000000000000000051271472324501400213120ustar00rootroot00000000000000module WEBrick module HTTPServlet class DefaultFileHandler < AbstractServlet @local_path: String def initialize: (HTTPServer server, String local_path) -> void def do_GET: (HTTPRequest req, HTTPResponse res) -> void def not_modified?: (HTTPRequest req, HTTPResponse res, Time mtime, String etag) -> bool # returns a lambda for webrick/httpresponse.rb send_body_proc def multipart_body: (File body, Array[[Numeric, Numeric]] parts, String boundary, String mtype, Integer filesize) -> HTTPResponse::_CallableBody def make_partial_content: (HTTPRequest req, HTTPResponse res, String filename, Integer filesize) -> void def prepare_range: (Range[Integer] range, Integer filesize) -> [Numeric, Numeric] end class FileHandler < AbstractServlet @config: AbstractServlet::_Config @logger: Log @root: String @options: Hash[Symbol, untyped] HandlerTable: Hash[String, singleton(AbstractServlet)] def self.add_handler: (String suffix, singleton(AbstractServlet) handler) -> singleton(AbstractServlet) def self.remove_handler: (String suffix) -> singleton(AbstractServlet) def initialize: (HTTPServer server, String root, ?Hash[Symbol, untyped] options, ?Hash[Symbol, untyped] default) -> void def set_filesystem_encoding: (String str) -> String def service: (HTTPRequest req, HTTPResponse res) -> void def do_GET: (HTTPRequest req, HTTPResponse res) -> void def do_POST: (HTTPRequest req, HTTPResponse res) -> void def do_OPTIONS: (HTTPRequest req, HTTPResponse res) -> void private def trailing_pathsep?: (String path) -> bool def prevent_directory_traversal: (HTTPRequest req, HTTPResponse res) -> void def exec_handler: (HTTPRequest req, HTTPResponse res) -> bool def get_handler: (HTTPRequest req, HTTPResponse res) -> singleton(AbstractServlet) def set_filename: (HTTPRequest req, HTTPResponse res) -> bool def check_filename: (HTTPRequest req, HTTPResponse res, String name) -> void def shift_path_info: (HTTPRequest req, HTTPResponse res, String path_info, ?String? base) -> void def search_index_file: (HTTPRequest req, HTTPResponse res) -> String? def search_file: (HTTPRequest req, HTTPResponse res, String basename) -> String? def call_callback: (Symbol callback_name, HTTPRequest req, HTTPResponse res) -> void def windows_ambiguous_name?: (String name) -> bool def nondisclosure_name?: (String name) -> bool def set_dir_list: (HTTPRequest req, HTTPResponse res) -> void end end end webrick-1.9.1/sig/httpservlet/prochandler.rbs000066400000000000000000000007301472324501400213310ustar00rootroot00000000000000module WEBrick module HTTPServlet class ProcHandler < AbstractServlet interface _Callable def call: (WEBrick::HTTPRequest, WEBrick::HTTPResponse) -> void end @proc: _Callable def get_instance: (HTTPServer server, *untyped options) -> self def initialize: (_Callable proc) -> void def do_GET: (HTTPRequest request, HTTPResponse response) -> void alias do_POST do_GET alias do_PUT do_GET end end end webrick-1.9.1/sig/httpstatus.rbs000066400000000000000000000142421472324501400166720ustar00rootroot00000000000000module 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 attr_reader self.code: Integer attr_reader self.reason_phrase: String # Returns the HTTP status code def code: () -> Integer # Returns the HTTP status description def reason_phrase: () -> String alias to_i code 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: Hash[Integer, String] # Maps a status code to the corresponding Status class CodeToError: Hash[Integer, singleton(Status)] # WEBrick::HTTPStatus::constants.grep(/\ARC_/).map{"#{_1}: #{WEBrick::HTTPStatus.const_get(_1)}"} RC_CONTINUE: 100 RC_SWITCHING_PROTOCOLS: 101 RC_OK: 200 RC_CREATED: 201 RC_ACCEPTED: 202 RC_NON_AUTHORITATIVE_INFORMATION: 203 RC_NO_CONTENT: 204 RC_RESET_CONTENT: 205 RC_PARTIAL_CONTENT: 206 RC_MULTI_STATUS: 207 RC_MULTIPLE_CHOICES: 300 RC_MOVED_PERMANENTLY: 301 RC_FOUND: 302 RC_SEE_OTHER: 303 RC_NOT_MODIFIED: 304 RC_USE_PROXY: 305 RC_TEMPORARY_REDIRECT: 307 RC_BAD_REQUEST: 400 RC_UNAUTHORIZED: 401 RC_PAYMENT_REQUIRED: 402 RC_FORBIDDEN: 403 RC_NOT_FOUND: 404 RC_METHOD_NOT_ALLOWED: 405 RC_NOT_ACCEPTABLE: 406 RC_PROXY_AUTHENTICATION_REQUIRED: 407 RC_REQUEST_TIMEOUT: 408 RC_CONFLICT: 409 RC_GONE: 410 RC_PRECONDITION_FAILED: 412 RC_LENGTH_REQUIRED: 411 RC_REQUEST_ENTITY_TOO_LARGE: 413 RC_REQUEST_URI_TOO_LARGE: 414 RC_UNSUPPORTED_MEDIA_TYPE: 415 RC_EXPECTATION_FAILED: 417 RC_UNPROCESSABLE_ENTITY: 422 RC_LOCKED: 423 RC_FAILED_DEPENDENCY: 424 RC_REQUEST_RANGE_NOT_SATISFIABLE: 416 RC_UPGRADE_REQUIRED: 426 RC_PRECONDITION_REQUIRED: 428 RC_TOO_MANY_REQUESTS: 429 RC_REQUEST_HEADER_FIELDS_TOO_LARGE: 431 RC_UNAVAILABLE_FOR_LEGAL_REASONS: 451 RC_INTERNAL_SERVER_ERROR: 500 RC_NOT_IMPLEMENTED: 501 RC_BAD_GATEWAY: 502 RC_SERVICE_UNAVAILABLE: 503 RC_GATEWAY_TIMEOUT: 504 RC_HTTP_VERSION_NOT_SUPPORTED: 505 RC_INSUFFICIENT_STORAGE: 507 RC_NETWORK_AUTHENTICATION_REQUIRED: 511 # WEBrick::HTTPStatus::CodeToError.each_value.map{"class #{_1.name.split(/::/).last} < #{_1.superclass.name.split(/::/).last}\nend"} class Continue < Info end class SwitchingProtocols < Info end class OK < Success end class Created < Success end class Accepted < Success end class NonAuthoritativeInformation < Success end class NoContent < Success end class ResetContent < Success end class PartialContent < Success end class MultiStatus < Success end class MultipleChoices < Redirect end class MovedPermanently < Redirect end class Found < Redirect end class SeeOther < Redirect end class NotModified < Redirect end class UseProxy < Redirect end class TemporaryRedirect < Redirect end class BadRequest < ClientError end class Unauthorized < ClientError end class PaymentRequired < ClientError end class Forbidden < ClientError end class NotFound < ClientError end class MethodNotAllowed < ClientError end class NotAcceptable < ClientError end class ProxyAuthenticationRequired < ClientError end class RequestTimeout < ClientError end class Conflict < ClientError end class Gone < ClientError end class LengthRequired < ClientError end class PreconditionFailed < ClientError end class RequestEntityTooLarge < ClientError end class RequestURITooLarge < ClientError end class UnsupportedMediaType < ClientError end class RequestRangeNotSatisfiable < ClientError end class ExpectationFailed < ClientError end class UnprocessableEntity < ClientError end class Locked < ClientError end class FailedDependency < ClientError end class UpgradeRequired < ClientError end class PreconditionRequired < ClientError end class TooManyRequests < ClientError end class RequestHeaderFieldsTooLarge < ClientError end class UnavailableForLegalReasons < ClientError end class InternalServerError < ServerError end class NotImplemented < ServerError end class BadGateway < ServerError end class ServiceUnavailable < ServerError end class GatewayTimeout < ServerError end class HTTPVersionNotSupported < ServerError end class InsufficientStorage < ServerError end class NetworkAuthenticationRequired < ServerError end # # Returns the description corresponding to the HTTP status +code+ # # WEBrick::HTTPStatus.reason_phrase 404 # => "Not Found" def self?.reason_phrase: (Integer code) -> String # # Is +code+ an informational status? def self?.info?: (Integer code) -> bool # # Is +code+ a successful status? def self?.success?: (Integer code) -> bool # # Is +code+ a redirection status? def self?.redirect?: (Integer code) -> bool # # Is +code+ an error status? def self?.error?: (Integer code) -> bool # # Is +code+ a client error status? def self?.client_error?: (Integer code) -> bool # # Is +code+ a server error status? def self?.server_error?: (Integer code) -> bool # # Returns the status class corresponding to +code+ # # WEBrick::HTTPStatus[302] # => WEBrick::HTTPStatus::NotFound # def self.[]: (Integer code) -> singleton(Status) end end webrick-1.9.1/sig/httputils.rbs000066400000000000000000000053111472324501400165040ustar00rootroot00000000000000module WEBrick CR: String LF: String CRLF: String module HTTPUtils def self?.normalize_path: (String path) -> String type mime_types = Hash[String, String] DefaultMimeTypes: mime_types def self?.load_mime_types: (string | _ToPath file) -> mime_types def self?.mime_type: (String filename, mime_types mime_tab) -> String class SplitHeader < Array[String] def join: (?String separator) -> String end class CookieHeader < Array[String] def join: (?String separator) -> String end HEADER_CLASSES: Hash[String, untyped] def self?.parse_header: (String raw) -> Hash[String, Array[String]] def self?.split_header_value: (String str) -> Array[String] def self?.parse_range_header: (String? ranges_specifier) -> Array[Range[Integer]]? def self?.parse_qvalues: (String? value) -> Array[String] def self?.dequote: (String str) -> String def self?.quote: (String str) -> String class FormData < String @raw_header: Array[String] @header: Hash[String, Array[String]] EmptyRawHeader: Array[String] EmptyHeader: Hash[String, Array[String]] attr_accessor name: String? attr_accessor filename: String? attr_accessor next_data: instance? def initialize: (*String args) -> void def []: (*String key) -> String # following is as same as String#[] | (int start, ?int length) -> String? | (range[int?] range) -> String? | (Regexp regexp, ?MatchData::capture backref) -> String? | (String substring) -> String? def <<: (String str) -> self def append_data: (instance data) -> self def each_data: () { (instance) -> void } -> void def list: () -> Array[String] alias to_ary list def to_s: () -> String end def self?.parse_query: (String? str) -> Hash[String, FormData] interface _EachLine def each_line: () { (String) -> void } -> void end def self?.parse_form_data: (_EachLine? io, interned boundary) -> Hash[String, FormData] def self?._make_regex: (String str) -> Regexp def self?._make_regex!: (String str) -> Regexp def self?._escape: (String str, Regexp regex) -> String def self?._unescape: (String str, Regexp regex) -> String UNESCAPED: Regexp UNESCAPED_FORM: Regexp NONASCII: Regexp ESCAPED: Regexp UNESCAPED_PCHAR: Regexp def self?.escape: (String str) -> String def self?.unescape: (String str) -> String def self?.escape_form: (String str) -> String def self?.unescape_form: (String str) -> String def self?.escape_path: (String str) -> String def self?.escape8bit: (String str) -> String end end webrick-1.9.1/sig/httpversion.rbs000066400000000000000000000005311472324501400170300ustar00rootroot00000000000000module WEBrick class HTTPVersion include Comparable attr_accessor major: Integer attr_accessor minor: Integer def self.convert: (HTTPVersion | String version) -> instance def initialize: (HTTPVersion | String version) -> void def <=>: (HTTPVersion | String other) -> Integer? def to_s: () -> String end end webrick-1.9.1/sig/log.rbs000066400000000000000000000041371472324501400152320ustar00rootroot00000000000000module WEBrick class BasicLog @log: IO? @opened: TrueClass? FATAL: 1 ERROR: 2 WARN: 3 INFO: 4 DEBUG: 5 # log-level, messages above this level will be logged attr_accessor level: Integer type log_file = (IO | String)? def initialize: (?log_file log_file, ?Integer? level) -> void # # Closes the logger (also closes the log device associated to the logger) def close: () -> void def log: (Integer level, String data) -> IO? # # Synonym for log(INFO, obj.to_s) def <<: (_ToS obj) -> IO? type message = Exception | _ToStr | Object # Shortcut for logging a FATAL message def fatal: (message msg) -> IO? # Shortcut for logging an ERROR message def error: (message msg) -> IO? # Shortcut for logging a WARN message def warn: (message msg) -> IO? # Shortcut for logging an INFO message def info: (message msg) -> IO? # Shortcut for logging a DEBUG message def debug: (message msg) -> IO? # Will the logger output FATAL messages? def fatal?: () -> bool # Will the logger output ERROR messages? def error?: () -> bool # Will the logger output WARN messages? def warn?: () -> bool # Will the logger output INFO messages? def info?: () -> bool # Will the logger output DEBUG messages? def debug?: () -> bool 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: (message arg) -> String end class Log < BasicLog # Format of the timestamp which is applied to each logged line. The # default is "[%Y-%m-%d %H:%M:%S]" attr_accessor time_format: String # # Same as BasicLog#initialize # # You can set the timestamp format through #time_format def initialize: (?BasicLog::log_file log_file, ?Integer? level) -> void # # Same as BasicLog#log def log: (Integer level, String data) -> IO? end end webrick-1.9.1/sig/manifest.yaml000066400000000000000000000002051472324501400164230ustar00rootroot00000000000000dependencies: - name: digest - name: erb - name: openssl - name: singleton - name: socket - name: tempfile - name: uri webrick-1.9.1/sig/server.rbs000066400000000000000000000024151472324501400157540ustar00rootroot00000000000000module WEBrick class ServerError < StandardError end class SimpleServer def self.start: [T] () { () -> T } -> T end class Daemon def self.start: () -> void | [T] () { () -> T } -> T end class GenericServer @shutdown_pipe: [IO, IO]? attr_reader status: :Stop | :Running | :Shutdown attr_reader config: Hash[Symbol, untyped] attr_reader logger: BasicLog attr_reader tokens: Thread::SizedQueue attr_reader listeners: Array[TCPServer| OpenSSL::SSL::SSLServer] def initialize: (?Hash[Symbol, untyped] config, ?Hash[Symbol, untyped] default) -> void def []: (Symbol key) -> untyped def listen: (String address, Integer port) -> void def start: () ?{ (TCPSocket) -> void } -> void def stop: () -> void def shutdown: () -> void def run: (TCPSocket sock) -> void private def accept_client: (TCPServer svr) -> TCPSocket? def start_thread: (TCPSocket sock) ?{ (TCPSocket) -> void } -> Thread def call_callback: (Symbol callback_name, *untyped args) -> untyped def setup_shutdown_pipe: () -> [IO, IO] def cleanup_shutdown_pipe: ([IO, IO]? shutdown_pipe) -> void def alarm_shutdown_pipe: [T] () { (IO) -> T } -> T? def cleanup_listener: () -> void end end webrick-1.9.1/sig/ssl.rbs000066400000000000000000000007601472324501400152500ustar00rootroot00000000000000module WEBrick module Config SSL: Hash[Symbol, untyped] end module Utils def self?.create_self_signed_cert: (untyped bits, untyped cn, untyped comment) -> ::Array[untyped] end class GenericServer @ssl_context: OpenSSL::SSL::SSLContext? def ssl_context: () -> OpenSSL::SSL::SSLContext? def setup_ssl_context: (Hash[Symbol, untyped] config) -> OpenSSL::SSL::SSLContext def ssl_servername_callback: (untyped sslsocket, ?untyped? hostname) -> untyped end end webrick-1.9.1/sig/utils.rbs000066400000000000000000000061211472324501400156040ustar00rootroot00000000000000module WEBrick module Utils # # Sets IO operations on +io+ to be non-blocking def self?.set_non_blocking: (IO io) -> void # # Sets the close on exec flag for +io+ def self?.set_close_on_exec: (IO io) -> void # # Changes the process's uid and gid to the ones of +user+ def self?.su: (String user) -> void # # The server hostname def self?.getservername: () -> String # # Creates TCP server sockets bound to +address+:+port+ and returns them. # # It will create IPV4 and IPV6 sockets on all interfaces. def self?.create_listeners: (String host, Integer port) -> Array[TCPServer] # # Characters used to generate random strings RAND_CHARS: String # # Generates a random string of length +len+ def self?.random_string: (Integer len) -> String # # 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 @queue: Thread::Queue @watcher: Thread? include Singleton # # Mutex used to synchronize access across threads TimeoutMutex: Thread::Mutex # # Registers a new timeout handler # # +time+:: Timeout in seconds # +exception+:: Exception to raise when timeout elapsed def self.register: (Numeric seconds, singleton(Exception) exception) -> Integer # # Cancels the timeout handler +id+ def self.cancel: (Integer id) -> bool def self.terminate: () -> Thread? # # Creates a new TimeoutHandler. You should use ::register and ::cancel # instead of creating the timeout handler directly. def initialize: () -> void private def watch: () -> bot def watcher: () -> Thread public # # Interrupts the timeout handler +id+ and raises +exception+ def interrupt: (Thread thread, Integer id, singleton(Exception) exception) -> nil # # Registers a new timeout handler # # +time+:: Timeout in seconds # +exception+:: Exception to raise when timeout elapsed def register: (Thread thread, Numeric time, singleton(Exception) exception) -> Integer # # Cancels the timeout handler +id+ def cancel: (Thread thread, Integer id) -> bool # def terminate: () -> Thread? 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 self?.timeout: [T] (Numeric? seconds, ?singleton(Exception) exception) { (?Numeric) -> T } -> T end end webrick-1.9.1/sig/version.rbs000066400000000000000000000000451472324501400161300ustar00rootroot00000000000000module WEBrick VERSION: String end webrick-1.9.1/test/000077500000000000000000000000001472324501400141315ustar00rootroot00000000000000webrick-1.9.1/test/lib/000077500000000000000000000000001472324501400146775ustar00rootroot00000000000000webrick-1.9.1/test/lib/helper.rb000066400000000000000000000003041472324501400165000ustar00rootroot00000000000000require "test/unit" require "core_assertions" Test::Unit::TestCase.include Test::Unit::CoreAssertions module TestWEBrick include Test::Unit::Util::Output extend Test::Unit::Util::Output end webrick-1.9.1/test/webrick/000077500000000000000000000000001472324501400155575ustar00rootroot00000000000000webrick-1.9.1/test/webrick/.htaccess000066400000000000000000000000431472324501400173520ustar00rootroot00000000000000this file should not be published. webrick-1.9.1/test/webrick/test_cgi.rb000066400000000000000000000124131472324501400177060ustar00rootroot00000000000000# 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 webrick-1.9.1/test/webrick/test_config.rb000066400000000000000000000010111472324501400204010ustar00rootroot00000000000000# 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 webrick-1.9.1/test/webrick/test_cookie.rb000066400000000000000000000116521472324501400204210ustar00rootroot00000000000000# 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 webrick-1.9.1/test/webrick/test_do_not_reverse_lookup.rb000066400000000000000000000052061472324501400235540ustar00rootroot00000000000000# 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 webrick-1.9.1/test/webrick/test_filehandler.rb000066400000000000000000000340341472324501400214240ustar00rootroot00000000000000# 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 = <<~HTTP.gsub("\n", "\r\n") GET / HTTP/1.0 Range: #{range_spec} HTTP return StringIO.new(msg) 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 webrick-1.9.1/test/webrick/test_htgroup.rb000066400000000000000000000011411472324501400206300ustar00rootroot00000000000000require "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 webrick-1.9.1/test/webrick/test_htmlutils.rb000066400000000000000000000014211472324501400211660ustar00rootroot00000000000000# 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 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 webrick-1.9.1/test/webrick/test_httpproxy.rb000066400000000000000000000431451472324501400212330ustar00rootroot00000000000000# 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 # 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 webrick-1.9.1/test/webrick/test_httprequest.rb000066400000000000000000000507361472324501400215460ustar00rootroot00000000000000# 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 = <<~HTTP.gsub("\n", "\r\n") GET / HTTP 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 = <<~HTTP.gsub("\n", "\r\n") GET / foobar # HTTP/0.9 request don't have header nor entity body. HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) 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 = <<~HTTP.gsub("\n", "\r\n") GET / HTTP/1.0 HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) 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 = <<~HTTP.gsub("\n", "\r\n") GET /path HTTP/1.1 HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) 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 = <<~HTTP.gsub("\n", "\r\n") GET /#{"a"*2084} HTTP/1.1 HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) assert_raise(WEBrick::HTTPStatus::RequestURITooLarge){ req.parse(StringIO.new(msg)) } end def test_invalid_content_length_header ['', ' ', ' +1', ' -1', ' a'].each do |cl| msg = <<~HTTP.gsub("\n", "\r\n") GET / HTTP/1.1 Content-Length:#{cl} HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) assert_raise(WEBrick::HTTPStatus::BadRequest){ req.parse(StringIO.new(msg)) } end end def test_bare_lf_request_line msg = <<~HTTP.gsub("\n", "\r\n") GET / HTTP/1.1 Content-Length: 0\r \r HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) assert_raise(WEBrick::HTTPStatus::EOFError){ req.parse(StringIO.new(msg)) } end def test_bare_lf_header msg = <<~HTTP.gsub("\n", "\r\n") GET / HTTP/1.1\r Content-Length: 0 \r HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) assert_raise(WEBrick::HTTPStatus::BadRequest){ req.parse(StringIO.new(msg)) } end def test_header_vt_ff_whitespace msg = <<~HTTP GET / HTTP/1.1\r Foo: \x0b1\x0c\r \r HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) assert_equal("\x0b1\x0c", req["Foo"]) msg = <<~HTTP GET / HTTP/1.1\r Foo: \x0b1\x0c\r \x0b2\x0c\r \r HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) assert_equal("\x0b1\x0c \x0b2\x0c", req["Foo"]) end def test_bare_cr_request_line msg = <<~HTTP.gsub("\n", "\r\n") GET / HTTP/1.1\r\r Content-Length: 0\r \r HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) assert_raise(WEBrick::HTTPStatus::BadRequest){ req.parse(StringIO.new(msg)) } end def test_bare_cr_header msg = <<~HTTP.gsub("\n", "\r\n") GET / HTTP/1.1\r Content-Type: foo\rbar\r \r HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) assert_raise(WEBrick::HTTPStatus::BadRequest){ req.parse(StringIO.new(msg)) } end def test_invalid_request_lines msg = <<~HTTP.gsub("\n", "\r\n") GET / HTTP/1.1\r Content-Length: 0\r \r HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) assert_raise(WEBrick::HTTPStatus::BadRequest){ req.parse(StringIO.new(msg)) } msg = <<~HTTP.gsub("\n", "\r\n") GET / HTTP/1.1\r Content-Length: 0\r \r HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) assert_raise(WEBrick::HTTPStatus::BadRequest){ req.parse(StringIO.new(msg)) } msg = <<~HTTP.gsub("\n", "\r\n") GET /\r HTTP/1.1\r Content-Length: 0\r \r HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) assert_raise(WEBrick::HTTPStatus::BadRequest){ req.parse(StringIO.new(msg)) } msg = <<~HTTP.gsub("\n", "\r\n") GET / HTTP/1.1 \r Content-Length: 0\r \r HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) assert_raise(WEBrick::HTTPStatus::BadRequest){ req.parse(StringIO.new(msg)) } end def test_duplicate_content_length_header msg = <<~HTTP.gsub("\n", "\r\n") GET / HTTP/1.1 Content-Length: 1 Content-Length: 2 HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) assert_raise(WEBrick::HTTPStatus::BadRequest){ req.parse(StringIO.new(msg)) } end def test_content_length_and_transfer_encoding_headers_smuggling msg = <<~HTTP.gsub("\n", "\r\n") POST /user HTTP/1.1 Content-Length: 28 Transfer-Encoding: chunked 0 GET /admin HTTP/1.1 HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) assert_raise(WEBrick::HTTPStatus::BadRequest){ req.body } end def test_parse_headers msg = <<~HTTP.gsub("\n", "\r\n") 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: 8 X-Empty-Header: foobar HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) 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(8, req.content_length) assert_equal("text/plain", req.content_type) assert_equal("foobar\r\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 = <<~HTTP.gsub("\n", "\r\n") POST /foo/bar/../baz?q=a HTTP/1.0 Content-Length: 10 User-Agent: FOO BAR BAZ hogehoge HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) 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("10", req['content-length']) assert_equal("FOO BAR BAZ", req['user-agent']) assert_equal("hogehoge\r\n", req.body) end def test_parse_headers3 msg = <<~HTTP.gsub("\n", "\r\n") GET /path HTTP/1.1 Host: test.ruby-lang.org HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) 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 = <<~HTTP.gsub("\n", "\r\n") GET /path HTTP/1.1 Host: 192.168.1.1 HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) 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 = <<~HTTP.gsub("\n", "\r\n") GET /path HTTP/1.1 Host: [fe80::208:dff:feef:98c7] HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) 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 = <<~HTTP.gsub("\n", "\r\n") GET /path HTTP/1.1 Host: 192.168.1.1:8080 HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) 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 = <<~HTTP.gsub("\n", "\r\n") GET /path HTTP/1.1 Host: [fe80::208:dff:feef:98c7]:8080 HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) 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 = <<~HTTP.gsub("\n", "\r\n") GET /path?#{param} HTTP/1.1 Host: test.ruby-lang.org:8080 HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) 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 = <<~HTTP.gsub("\n", "\r\n") 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} HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) 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 = <<~HTTP.gsub("\n", "\r\n") POST /path HTTP/1.1 Host: test.ruby-lang.org:8080 Transfer-Encoding: chunked HTTP 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_bad_chunked msg = <<~HTTP POST /path HTTP/1.1\r Transfer-Encoding: chunked\r \r 01x1\r \r 1 HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) assert_raise(WEBrick::HTTPStatus::BadRequest){ req.body } # chunked req.body_reader req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) dst = StringIO.new assert_raise(WEBrick::HTTPStatus::BadRequest) do IO.copy_stream(req.body_reader, dst) end end def test_bad_chunked_extra_data msg = <<~HTTP POST /path HTTP/1.1\r Transfer-Encoding: chunked\r \r 3\r ABCthis-all-gets-ignored\r 0\r \r HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) assert_raise(WEBrick::HTTPStatus::BadRequest){ req.body } # chunked req.body_reader req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) dst = StringIO.new assert_raise(WEBrick::HTTPStatus::BadRequest) do IO.copy_stream(req.body_reader, dst) end end def test_null_byte_in_header msg = <<~HTTP.gsub("\n", "\r\n") POST /path HTTP/1.1\r Evil: evil\x00\r \r HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) assert_raise(WEBrick::HTTPStatus::BadRequest){ req.parse(StringIO.new(msg)) } end def test_forwarded msg = <<~HTTP.gsub("\n", "\r\n") 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 HTTP 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 = <<~HTTP.gsub("\n", "\r\n") 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 HTTP 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 = <<~HTTP.gsub("\n", "\r\n") 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 HTTP 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 = <<~HTTP.gsub("\n", "\r\n") 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 HTTP 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 = <<~HTTP.gsub("\n", "\r\n") 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 HTTP 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 = <<~HTTP.gsub("\n", "\r\n") 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 HTTP 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 = <<~HTTP.gsub("\n", "\r\n") POST /path HTTP/1.1 Expect: 100-continue HTTP 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 = <<~HTTP.gsub("\n", "\r\n") POST /path HTTP/1.1 HTTP 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_bad_messages param = "foo=1;foo=2;foo=3;bar=x" msg = <<~HTTP.gsub("\n", "\r\n") 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} HTTP assert_raise(WEBrick::HTTPStatus::LengthRequired){ req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) req.body } msg = <<~HTTP.gsub("\n", "\r\n") 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. HTTP assert_raise(WEBrick::HTTPStatus::BadRequest){ req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) req.body } msg = <<~HTTP.gsub("\n", "\r\n") 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. HTTP assert_raise(WEBrick::HTTPStatus::NotImplemented){ req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new(msg)) 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 def test_eof_raised_with_missing_line_between_headers_and_body msg = <<~HTTP.gsub("\n", "\r\n") GET / HTTP/1.0 HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) assert_raise(WEBrick::HTTPStatus::EOFError) { req.parse(StringIO.new(msg)) } msg = <<~HTTP.gsub("\n", "\r\n") GET / HTTP/1.0 Foo: 1 HTTP req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) assert_raise(WEBrick::HTTPStatus::EOFError) { req.parse(StringIO.new(msg)) } end def test_cookie_join req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) req.parse(StringIO.new("GET / HTTP/1.1\r\ncookie: a=1\r\ncookie: b=2\r\n\r\n")) assert_equal 2, req.cookies.length assert_equal 'a=1; b=2', req['cookie'] end end webrick-1.9.1/test/webrick/test_httpresponse.rb000066400000000000000000000175761472324501400217210ustar00rootroot00000000000000# frozen_string_literal: false require "webrick" 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_response_body_not_frozen refute @res.body.frozen? 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' 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(/ "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 webrick-1.9.1/test/webrick/test_httpserver.rb000066400000000000000000000510571472324501400213610ustar00rootroot00000000000000# 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::MJIT) && RubyVM::MJIT.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_empty log } 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 def test_accept_put_requests TestWEBrick.start_httpserver do |server, addr, port, log| server.mount_proc("/", lambda {|req, res| res.status = 200 assert_equal("abcde", req.body) }) Thread.pass while server.status != :Running Net::HTTP.start(addr, port) do |http| req = Net::HTTP::Put.new("/") req.body = "abcde" req['content-type'] = "text/plain" http.request(req) do |res| assert_equal("200", res.code) end server.shutdown end end end end webrick-1.9.1/test/webrick/test_httpstatus.rb000066400000000000000000000015141472324501400213670ustar00rootroot00000000000000# 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 webrick-1.9.1/test/webrick/test_httputils.rb000066400000000000000000000110041472324501400211770ustar00rootroot00000000000000# 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 webrick-1.9.1/test/webrick/test_httpversion.rb000066400000000000000000000016601472324501400215330ustar00rootroot00000000000000# 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 webrick-1.9.1/test/webrick/test_server.rb000066400000000000000000000124341472324501400204550ustar00rootroot00000000000000# 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::MJIT) && RubyVM::MJIT.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 webrick-1.9.1/test/webrick/test_ssl_server.rb000066400000000000000000000032701472324501400213340ustar00rootroot00000000000000require "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 webrick-1.9.1/test/webrick/test_utils.rb000066400000000000000000000051451472324501400203100ustar00rootroot00000000000000# 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 webrick-1.9.1/test/webrick/utils.rb000066400000000000000000000067351472324501400172570ustar00rootroot00000000000000# 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 webrick-1.9.1/test/webrick/webrick.cgi000066400000000000000000000017641472324501400177010ustar00rootroot00000000000000#!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 webrick-1.9.1/test/webrick/webrick.rhtml000066400000000000000000000001171472324501400202540ustar00rootroot00000000000000req to <%= servlet_request.request_uri %> <%= servlet_request.query.inspect %> webrick-1.9.1/test/webrick/webrick_long_filename.cgi000066400000000000000000000016431472324501400225540ustar00rootroot00000000000000#!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 webrick-1.9.1/webrick.gemspec000066400000000000000000000060761472324501400161560ustar00rootroot00000000000000# frozen_string_literal: true begin require_relative 'lib/webrick/version' rescue LoadError # for Ruby core repository require_relative 'version' end Gem::Specification.new do |s| s.name = "webrick" s.version = WEBrick::VERSION s.summary = "HTTP server toolkit" s.description = "WEBrick is an HTTP server toolkit that can be configured as an HTTPS server, a proxy server, and a virtual-host server." s.require_path = %w{lib} s.files = [ "Gemfile", "LICENSE.txt", "README.md", "Rakefile", "lib/webrick.rb", "lib/webrick/accesslog.rb", "lib/webrick/cgi.rb", "lib/webrick/compat.rb", "lib/webrick/config.rb", "lib/webrick/cookie.rb", "lib/webrick/htmlutils.rb", "lib/webrick/httpauth.rb", "lib/webrick/httpauth/authenticator.rb", "lib/webrick/httpauth/basicauth.rb", "lib/webrick/httpauth/digestauth.rb", "lib/webrick/httpauth/htdigest.rb", "lib/webrick/httpauth/htgroup.rb", "lib/webrick/httpauth/htpasswd.rb", "lib/webrick/httpauth/userdb.rb", "lib/webrick/httpproxy.rb", "lib/webrick/httprequest.rb", "lib/webrick/httpresponse.rb", "lib/webrick/https.rb", "lib/webrick/httpserver.rb", "lib/webrick/httpservlet.rb", "lib/webrick/httpservlet/abstract.rb", "lib/webrick/httpservlet/cgi_runner.rb", "lib/webrick/httpservlet/cgihandler.rb", "lib/webrick/httpservlet/erbhandler.rb", "lib/webrick/httpservlet/filehandler.rb", "lib/webrick/httpservlet/prochandler.rb", "lib/webrick/httpstatus.rb", "lib/webrick/httputils.rb", "lib/webrick/httpversion.rb", "lib/webrick/log.rb", "lib/webrick/server.rb", "lib/webrick/ssl.rb", "lib/webrick/utils.rb", "lib/webrick/version.rb", "sig/accesslog.rbs", "sig/cgi.rbs", "sig/compat.rbs", "sig/config.rbs", "sig/cookie.rbs", "sig/htmlutils.rbs", "sig/httpauth.rbs", "sig/httpauth/authenticator.rbs", "sig/httpauth/basicauth.rbs", "sig/httpauth/digestauth.rbs", "sig/httpauth/htdigest.rbs", "sig/httpauth/htgroup.rbs", "sig/httpauth/htpasswd.rbs", "sig/httpauth/userdb.rbs", "sig/httpproxy.rbs", "sig/httprequest.rbs", "sig/httpresponse.rbs", "sig/https.rbs", "sig/httpserver.rbs", "sig/httpservlet.rbs", "sig/httpservlet/abstract.rbs", "sig/httpservlet/cgi_runner.rbs", "sig/httpservlet/cgihandler.rbs", "sig/httpservlet/erbhandler.rbs", "sig/httpservlet/filehandler.rbs", "sig/httpservlet/prochandler.rbs", "sig/httpstatus.rbs", "sig/httputils.rbs", "sig/httpversion.rbs", "sig/log.rbs", "sig/manifest.yaml", "sig/server.rbs", "sig/ssl.rbs", "sig/utils.rbs", "sig/version.rbs", "webrick.gemspec", ] s.required_ruby_version = ">= 2.4.0" s.authors = ["TAKAHASHI Masayoshi", "GOTOU YUUZOU", "Eric Wong"] s.email = [nil, nil, 'normal@ruby-lang.org'] s.homepage = "https://github.com/ruby/webrick" s.licenses = ["Ruby", "BSD-2-Clause"] if s.respond_to?(:metadata=) s.metadata = { "bug_tracker_uri" => "https://github.com/ruby/webrick/issues", } end end