From 4a39167260fbd0e8accf42ef7dee27ae73159f8f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 28 Oct 2021 17:28:59 +0900 Subject: [PATCH] Removed the related code of `gem server` --- lib/rubygems/commands/server_command.rb | 91 +- lib/rubygems/server.rb | 882 ------------------ .../test_gem_commands_server_command.rb | 50 +- test/rubygems/test_gem_server.rb | 608 ------------ 4 files changed, 18 insertions(+), 1613 deletions(-) delete mode 100644 lib/rubygems/server.rb delete mode 100644 test/rubygems/test_gem_server.rb diff --git a/lib/rubygems/commands/server_command.rb b/lib/rubygems/commands/server_command.rb index f3c08efb5e..f8cad3b5db 100644 --- a/lib/rubygems/commands/server_command.rb +++ b/lib/rubygems/commands/server_command.rb @@ -1,88 +1,25 @@ # frozen_string_literal: true require_relative '../command' -require_relative '../server' -require_relative '../deprecate' -class Gem::Commands::ServerCommand < Gem::Command - extend Gem::Deprecate - rubygems_deprecate_command - - def initialize - super 'server', 'Documentation and gem repository HTTP server', - :port => 8808, :gemdir => [], :daemon => false - - OptionParser.accept :Port do |port| - if port =~ /\A\d+\z/ - port = Integer port - raise OptionParser::InvalidArgument, "#{port}: not a port number" if - port > 65535 - - port - else - begin - Socket.getservbyname port - rescue SocketError - raise OptionParser::InvalidArgument, "#{port}: no such named service" - end +unless defined? Gem::Commands::ServerCommand + class Gem::Commands::ServerCommand < Gem::Command + def initialize + super('server', 'Starts up a web server that hosts the RDoc (requires rubygems-server)') + begin + Gem::Specification.find_by_name('rubygems-server').activate + rescue Gem::LoadError + # no-op end end - add_option '-p', '--port=PORT', :Port, - 'port to listen on' do |port, options| - options[:port] = port + def description # :nodoc: + <<-EOF +The server command has been moved to the rubygems-server gem. + EOF end - add_option '-d', '--dir=GEMDIR', - 'directories from which to serve gems', - 'multiple directories may be provided' do |gemdir, options| - options[:gemdir] << File.expand_path(gemdir) + def execute + alert_error "Install the rubygems-server gem for the server command" end - - add_option '--[no-]daemon', 'run as a daemon' do |daemon, options| - options[:daemon] = daemon - end - - add_option '-b', '--bind=HOST,HOST', - 'addresses to bind', Array do |address, options| - options[:addresses] ||= [] - options[:addresses].push(*address) - end - - add_option '-l', '--launch[=COMMAND]', - 'launches a browser window', - "COMMAND defaults to 'start' on Windows", - "and 'open' on all other platforms" do |launch, options| - launch ||= Gem.win_platform? ? 'start' : 'open' - options[:launch] = launch - end - end - - def defaults_str # :nodoc: - "--port 8808 --dir #{Gem.dir} --no-daemon" - end - - def description # :nodoc: - <<-EOF -The server command starts up a web server that hosts the RDoc for your -installed gems and can operate as a server for installation of gems on other -machines. - -The cache files for installed gems must exist to use the server as a source -for gem installation. - -To install gems from a running server, use `gem install GEMNAME --source -http://gem_server_host:8808` - -You can set up a shortcut to gem server documentation using the URL: - - http://localhost:8808/rdoc?q=%s - Firefox - http://localhost:8808/rdoc?q=* - LaunchBar - - EOF - end - - def execute - options[:gemdir] = Gem.path if options[:gemdir].empty? - Gem::Server.run options end end diff --git a/lib/rubygems/server.rb b/lib/rubygems/server.rb deleted file mode 100644 index 45be05bcda..0000000000 --- a/lib/rubygems/server.rb +++ /dev/null @@ -1,882 +0,0 @@ -# frozen_string_literal: true -require 'zlib' -require 'erb' -require 'uri' - -require_relative '../rubygems' -require_relative 'rdoc' - -## -# Gem::Server and allows users to serve gems for consumption by -# `gem --remote-install`. -# -# gem_server starts an HTTP server on the given port and serves the following: -# * "/" - Browsing of gem spec files for installed gems -# * "/specs.#{Gem.marshal_version}.gz" - specs name/version/platform index -# * "/latest_specs.#{Gem.marshal_version}.gz" - latest specs -# name/version/platform index -# * "/quick/" - Individual gemspecs -# * "/gems" - Direct access to download the installable gems -# * "/rdoc?q=" - Search for installed rdoc documentation -# -# == Usage -# -# gem_server = Gem::Server.new Gem.dir, 8089, false -# gem_server.run -# -#-- -# TODO Refactor into a real WEBrick servlet to remove code duplication. - -class Gem::Server - attr_reader :spec_dirs - - include ERB::Util - include Gem::UserInteraction - - SEARCH = <<-ERB.freeze -
- -
- ERB - - DOC_TEMPLATE = <<-'ERB'.freeze - - - - - - RubyGems Documentation Index - - - -
-<%= SEARCH %> -

RubyGems Documentation Index

-
- - -
-
-
-

Summary

-

There are <%=values["gem_count"]%> gems installed:

-

- <%= values["specs"].map { |v| "#{h v["name"]}" }.join ', ' %>. -

Gems

- -
- <% values["specs"].each do |spec| %> -
- <% if spec["first_name_entry"] then %> - "> - <% end %> - - <%=h spec["name"]%> <%=h spec["version"]%> - - <% if spec["ri_installed"] || spec["rdoc_installed"] then %> - ">[rdoc] - <% else %> - [rdoc] - <% end %> - - <% if spec["homepage"] then %> - " title="<%=h spec["homepage"]%>">[www] - <% else %> - [www] - <% end %> - - <% if spec["has_deps"] then %> - - depends on - <%= spec["dependencies"].map { |v| "#{h v["name"]}" }.join ', ' %>. - <% end %> -
-
- <%=spec["summary"]%> - <% if spec["executables"] then %> -
- - <% if spec["only_one_executable"] then %> - Executable is - <% else %> - Executables are - <%end%> - - <%= spec["executables"].map { |v| "#{h v["executable"]}"}.join ', ' %>. - - <%end%> -
-
-
- <% end %> -
- -
-
-
-
-

[Validate]

-
- - - ERB - - # CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108 - RDOC_CSS = <<-CSS.freeze -body { - font-family: Verdana,Arial,Helvetica,sans-serif; - font-size: 90%; - margin: 0; - margin-left: 40px; - padding: 0; - background: white; -} - -h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; } -h1 { font-size: 150%; } -h2,h3,h4 { margin-top: 1em; } - -a { background: #eef; color: #039; text-decoration: none; } -a:hover { background: #039; color: #eef; } - -/* Override the base stylesheets Anchor inside a table cell */ -td > a { - background: transparent; - color: #039; - text-decoration: none; -} - -/* and inside a section title */ -.section-title > a { - background: transparent; - color: #eee; - text-decoration: none; -} - -/* === Structural elements =================================== */ - -div#index { - margin: 0; - margin-left: -40px; - padding: 0; - font-size: 90%; -} - - -div#index a { - margin-left: 0.7em; -} - -div#index .section-bar { - margin-left: 0px; - padding-left: 0.7em; - background: #ccc; - font-size: small; -} - - -div#classHeader, div#fileHeader { - width: auto; - color: white; - padding: 0.5em 1.5em 0.5em 1.5em; - margin: 0; - margin-left: -40px; - border-bottom: 3px solid #006; -} - -div#classHeader a, div#fileHeader a { - background: inherit; - color: white; -} - -div#classHeader td, div#fileHeader td { - background: inherit; - color: white; -} - - -div#fileHeader { - background: #057; -} - -div#classHeader { - background: #048; -} - - -.class-name-in-header { - font-size: 180%; - font-weight: bold; -} - - -div#bodyContent { - padding: 0 1.5em 0 1.5em; -} - -div#description { - padding: 0.5em 1.5em; - background: #efefef; - border: 1px dotted #999; -} - -div#description h1,h2,h3,h4,h5,h6 { - color: #125;; - background: transparent; -} - -div#validator-badges { - text-align: center; -} -div#validator-badges img { border: 0; } - -div#copyright { - color: #333; - background: #efefef; - font: 0.75em sans-serif; - margin-top: 5em; - margin-bottom: 0; - padding: 0.5em 2em; -} - - -/* === Classes =================================== */ - -table.header-table { - color: white; - font-size: small; -} - -.type-note { - font-size: small; - color: #DEDEDE; -} - -.xxsection-bar { - background: #eee; - color: #333; - padding: 3px; -} - -.section-bar { - color: #333; - border-bottom: 1px solid #999; - margin-left: -20px; -} - - -.section-title { - background: #79a; - color: #eee; - padding: 3px; - margin-top: 2em; - margin-left: -30px; - border: 1px solid #999; -} - -.top-aligned-row { vertical-align: top } -.bottom-aligned-row { vertical-align: bottom } - -/* --- Context section classes ----------------------- */ - -.context-row { } -.context-item-name { font-family: monospace; font-weight: bold; color: black; } -.context-item-value { font-size: small; color: #448; } -.context-item-desc { color: #333; padding-left: 2em; } - -/* --- Method classes -------------------------- */ -.method-detail { - background: #efefef; - padding: 0; - margin-top: 0.5em; - margin-bottom: 1em; - border: 1px dotted #ccc; -} -.method-heading { - color: black; - background: #ccc; - border-bottom: 1px solid #666; - padding: 0.2em 0.5em 0 0.5em; -} -.method-signature { color: black; background: inherit; } -.method-name { font-weight: bold; } -.method-args { font-style: italic; } -.method-description { padding: 0 0.5em 0 0.5em; } - -/* --- Source code sections -------------------- */ - -a.source-toggle { font-size: 90%; } -div.method-source-code { - background: #262626; - color: #ffdead; - margin: 1em; - padding: 0.5em; - border: 1px dashed #999; - overflow: hidden; -} - -div.method-source-code pre { color: #ffdead; overflow: hidden; } - -/* --- Ruby keyword styles --------------------- */ - -.standalone-code { background: #221111; color: #ffdead; overflow: hidden; } - -.ruby-constant { color: #7fffd4; background: transparent; } -.ruby-keyword { color: #00ffff; background: transparent; } -.ruby-ivar { color: #eedd82; background: transparent; } -.ruby-operator { color: #00ffee; background: transparent; } -.ruby-identifier { color: #ffdead; background: transparent; } -.ruby-node { color: #ffa07a; background: transparent; } -.ruby-comment { color: #b22222; font-weight: bold; background: transparent; } -.ruby-regexp { color: #ffa07a; background: transparent; } -.ruby-value { color: #7fffd4; background: transparent; } - CSS - - RDOC_NO_DOCUMENTATION = <<-'ERB'.freeze - - - - - Found documentation - - - -
-<%= SEARCH %> -

No documentation found

-
- -
-
-
-

No gems matched <%= h query.inspect %>

- -

- Back to complete gem index -

- -
-
-
-
-

[Validate]

-
- - - ERB - - RDOC_SEARCH_TEMPLATE = <<-'ERB'.freeze - - - - - Found documentation - - - -
-<%= SEARCH %> -

Found documentation

-
- - -
-
-
-

Summary

-

<%=doc_items.length%> documentation topics found.

-

Topics

- -
- <% doc_items.each do |doc_item| %> -
- <%=doc_item[:name]%> - [rdoc] -
-
- <%=h doc_item[:summary]%> -
-
-
- <% end %> -
- -

- Back to complete gem index -

- -
-
-
-
-

[Validate]

-
- - - ERB - - def self.run(options) - new(options[:gemdir], options[:port], options[:daemon], - options[:launch], options[:addresses]).run - end - - def initialize(gem_dirs, port, daemon, launch = nil, addresses = nil) - begin - require 'webrick' - rescue LoadError - abort "webrick is not found. You may need to `gem install webrick` to install webrick." - end - - Gem::RDoc.load_rdoc - Socket.do_not_reverse_lookup = true - - @gem_dirs = Array gem_dirs - @port = port - @daemon = daemon - @launch = launch - @addresses = addresses - - logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL - @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger - - @spec_dirs = @gem_dirs.map {|gem_dir| File.join gem_dir, 'specifications' } - @spec_dirs.reject! {|spec_dir| !File.directory? spec_dir } - - reset_gems - - @have_rdoc_4_plus = nil - end - - def add_date(res) - res['date'] = @spec_dirs.map do |spec_dir| - File.stat(spec_dir).mtime - end.max - end - - def uri_encode(str) - str.gsub(URI::UNSAFE) do |match| - match.each_byte.map {|c| sprintf('%%%02X', c.ord) }.join - end - end - - def doc_root(gem_name) - if have_rdoc_4_plus? - "/doc_root/#{u gem_name}/" - else - "/doc_root/#{u gem_name}/rdoc/index.html" - end - end - - def have_rdoc_4_plus? - @have_rdoc_4_plus ||= - Gem::Requirement.new('>= 4.0.0.preview2').satisfied_by? Gem::RDoc.rdoc_version - end - - def latest_specs(req, res) - reset_gems - - res['content-type'] = 'application/x-gzip' - - add_date res - - latest_specs = Gem::Specification.latest_specs - - specs = latest_specs.sort.map do |spec| - platform = spec.original_platform || Gem::Platform::RUBY - [spec.name, spec.version, platform] - end - - specs = Marshal.dump specs - - if req.path =~ /\.gz$/ - specs = Gem::Util.gzip specs - res['content-type'] = 'application/x-gzip' - else - res['content-type'] = 'application/octet-stream' - end - - if req.request_method == 'HEAD' - res['content-length'] = specs.length - else - res.body << specs - end - end - - ## - # Creates server sockets based on the addresses option. If no addresses - # were given a server socket for all interfaces is created. - - def listen(addresses = @addresses) - addresses = [nil] unless addresses - - listeners = 0 - - addresses.each do |address| - begin - @server.listen address, @port - @server.listeners[listeners..-1].each do |listener| - host, port = listener.addr.values_at 2, 1 - host = "[#{host}]" if host =~ /:/ # we don't reverse lookup - say "Server started at http://#{host}:#{port}" - end - - listeners = @server.listeners.length - rescue SystemCallError - next - end - end - - if @server.listeners.empty? - say "Unable to start a server." - say "Check for running servers or your --bind and --port arguments" - terminate_interaction 1 - end - end - - def prerelease_specs(req, res) - reset_gems - - res['content-type'] = 'application/x-gzip' - - add_date res - - specs = Gem::Specification.select do |spec| - spec.version.prerelease? - end.sort.map do |spec| - platform = spec.original_platform || Gem::Platform::RUBY - [spec.name, spec.version, platform] - end - - specs = Marshal.dump specs - - if req.path =~ /\.gz$/ - specs = Gem::Util.gzip specs - res['content-type'] = 'application/x-gzip' - else - res['content-type'] = 'application/octet-stream' - end - - if req.request_method == 'HEAD' - res['content-length'] = specs.length - else - res.body << specs - end - end - - def quick(req, res) - reset_gems - - res['content-type'] = 'text/plain' - add_date res - - case req.request_uri.path - when %r{^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)\.gemspec\.rz$} then - marshal_format, full_name = $1, $2 - specs = Gem::Specification.find_all_by_full_name(full_name) - - selector = full_name.inspect - - if specs.empty? - res.status = 404 - res.body = "No gems found matching #{selector}" - elsif specs.length > 1 - res.status = 500 - res.body = "Multiple gems found matching #{selector}" - elsif marshal_format - res['content-type'] = 'application/x-deflate' - res.body << Gem.deflate(Marshal.dump(specs.first)) - end - else - raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." - end - end - - def root(req, res) - reset_gems - - add_date res - - raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless - req.path == '/' - - specs = [] - total_file_count = 0 - - Gem::Specification.each do |spec| - total_file_count += spec.files.size - deps = spec.dependencies.map do |dep| - { - "name" => dep.name, - "type" => dep.type, - "version" => dep.requirement.to_s, - } - end - - deps = deps.sort_by {|dep| [dep["name"].downcase, dep["version"]] } - deps.last["is_last"] = true unless deps.empty? - - # executables - executables = spec.executables.sort.collect {|exec| {"executable" => exec} } - executables = nil if executables.empty? - executables.last["is_last"] = true if executables - - # Pre-process spec homepage for safety reasons - begin - homepage_uri = URI.parse(spec.homepage) - if [URI::HTTP, URI::HTTPS].member? homepage_uri.class - homepage_uri = spec.homepage - else - homepage_uri = "." - end - rescue URI::InvalidURIError - homepage_uri = "." - end - - specs << { - "authors" => spec.authors.sort.join(", "), - "date" => spec.date.to_s, - "dependencies" => deps, - "doc_path" => doc_root(spec.full_name), - "executables" => executables, - "only_one_executable" => (executables && executables.size == 1), - "full_name" => spec.full_name, - "has_deps" => !deps.empty?, - "homepage" => homepage_uri, - "name" => spec.name, - "rdoc_installed" => Gem::RDoc.new(spec).rdoc_installed?, - "ri_installed" => Gem::RDoc.new(spec).ri_installed?, - "summary" => spec.summary, - "version" => spec.version.to_s, - } - end - - specs << { - "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others", - "dependencies" => [], - "doc_path" => doc_root("rubygems-#{Gem::VERSION}"), - "executables" => [{"executable" => 'gem', "is_last" => true}], - "only_one_executable" => true, - "full_name" => "rubygems-#{Gem::VERSION}", - "has_deps" => false, - "homepage" => "https://guides.rubygems.org/", - "name" => 'rubygems', - "ri_installed" => true, - "summary" => "RubyGems itself", - "version" => Gem::VERSION, - } - - specs = specs.sort_by {|spec| [spec["name"].downcase, spec["version"]] } - specs.last["is_last"] = true - - # tag all specs with first_name_entry - last_spec = nil - specs.each do |spec| - is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase) - spec["first_name_entry"] = is_first - last_spec = spec - end - - # create page from template - template = ERB.new(DOC_TEMPLATE) - res['content-type'] = 'text/html' - - values = { "gem_count" => specs.size.to_s, "specs" => specs, - "total_file_count" => total_file_count.to_s } - - # suppress 1.9.3dev warning about unused variable - values = values - - result = template.result binding - res.body = result - end - - ## - # Can be used for quick navigation to the rdoc documentation. You can then - # define a search shortcut for your browser. E.g. in Firefox connect - # 'shortcut:rdoc' to http://localhost:8808/rdoc?q=%s template. Then you can - # directly open the ActionPack documentation by typing 'rdoc actionp'. If - # there are multiple hits for the search term, they are presented as a list - # with links. - # - # Search algorithm aims for an intuitive search: - # 1. first try to find the gems and documentation folders which name - # starts with the search term - # 2. search for entries, that *contain* the search term - # 3. show all the gems - # - # If there is only one search hit, user is immediately redirected to the - # documentation for the particular gem, otherwise a list with results is - # shown. - # - # === Additional trick - install documentation for Ruby core - # - # Note: please adjust paths accordingly use for example 'locate yaml.rb' and - # 'gem environment' to identify directories, that are specific for your - # local installation - # - # 1. install Ruby sources - # cd /usr/src - # sudo apt-get source ruby - # - # 2. generate documentation - # rdoc -o /usr/lib/ruby/gems/1.8/doc/core/rdoc \ - # /usr/lib/ruby/1.8 ruby1.8-1.8.7.72 - # - # By typing 'rdoc core' you can now access the core documentation - - def rdoc(req, res) - query = req.query['q'] - show_rdoc_for_pattern("#{query}*", res) && return - show_rdoc_for_pattern("*#{query}*", res) && return - - template = ERB.new RDOC_NO_DOCUMENTATION - - res['content-type'] = 'text/html' - res.body = template.result binding - end - - ## - # Updates the server to use the latest installed gems. - - def reset_gems # :nodoc: - Gem::Specification.dirs = @gem_dirs - end - - ## - # Returns true and prepares http response, if rdoc for the requested gem - # name pattern was found. - # - # The search is based on the file system content, not on the gems metadata. - # This allows additional documentation folders like 'core' for the Ruby core - # documentation - just put it underneath the main doc folder. - - def show_rdoc_for_pattern(pattern, res) - found_gems = Dir.glob("{#{@gem_dirs.join ','}}/doc/#{pattern}").select do |path| - File.exist? File.join(path, 'rdoc/index.html') - end - case found_gems.length - when 0 - return false - when 1 - new_path = File.basename(found_gems[0]) - res.status = 302 - res['Location'] = doc_root new_path - return true - else - doc_items = [] - found_gems.each do |file_name| - base_name = File.basename(file_name) - doc_items << { - :name => base_name, - :url => doc_root(new_path), - :summary => '', - } - end - - template = ERB.new(RDOC_SEARCH_TEMPLATE) - res['content-type'] = 'text/html' - result = template.result binding - res.body = result - return true - end - end - - def run - listen - - WEBrick::Daemon.start if @daemon - - @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs) - @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs) - - @server.mount_proc "/latest_specs.#{Gem.marshal_version}", - method(:latest_specs) - @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz", - method(:latest_specs) - - @server.mount_proc "/prerelease_specs.#{Gem.marshal_version}", - method(:prerelease_specs) - @server.mount_proc "/prerelease_specs.#{Gem.marshal_version}.gz", - method(:prerelease_specs) - - @server.mount_proc "/quick/", method(:quick) - - @server.mount_proc("/gem-server-rdoc-style.css") do |req, res| - res['content-type'] = 'text/css' - add_date res - res.body << RDOC_CSS - end - - @server.mount_proc "/", method(:root) - - @server.mount_proc "/rdoc", method(:rdoc) - - file_handlers = { - '/gems' => '/cache/', - } - - if have_rdoc_4_plus? - @server.mount '/doc_root', RDoc::Servlet, '/doc_root' - else - file_handlers['/doc_root'] = '/doc/' - end - - @gem_dirs.each do |gem_dir| - file_handlers.each do |mount_point, mount_dir| - @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler, - File.join(gem_dir, mount_dir), true) - end - end - - trap("INT") { @server.shutdown; exit! } - trap("TERM") { @server.shutdown; exit! } - - launch if @launch - - @server.start - end - - def specs(req, res) - reset_gems - - add_date res - - specs = Gem::Specification.sort_by(&:sort_obj).map do |spec| - platform = spec.original_platform || Gem::Platform::RUBY - [spec.name, spec.version, platform] - end - - specs = Marshal.dump specs - - if req.path =~ /\.gz$/ - specs = Gem::Util.gzip specs - res['content-type'] = 'application/x-gzip' - else - res['content-type'] = 'application/octet-stream' - end - - if req.request_method == 'HEAD' - res['content-length'] = specs.length - else - res.body << specs - end - end - - def launch - listeners = @server.listeners.map{|l| l.addr[2] } - - # TODO: 0.0.0.0 == any, not localhost. - host = listeners.any?{|l| l == '0.0.0.0' } ? 'localhost' : listeners.first - - say "Launching browser to http://#{host}:#{@port}" - - system("#{@launch} http://#{host}:#{@port}") - end -end diff --git a/test/rubygems/test_gem_commands_server_command.rb b/test/rubygems/test_gem_commands_server_command.rb index 09cec63c15..96c328f93f 100644 --- a/test/rubygems/test_gem_commands_server_command.rb +++ b/test/rubygems/test_gem_commands_server_command.rb @@ -9,53 +9,11 @@ class TestGemCommandsServerCommand < Gem::TestCase @cmd = Gem::Commands::ServerCommand.new end - def test_handle_options - @cmd.send :handle_options, %w[-p 8808 --no-daemon] - - assert_equal false, @cmd.options[:daemon] - assert_equal [], @cmd.options[:gemdir] - assert_equal 8808, @cmd.options[:port] - - @cmd.send :handle_options, %w[-p 9999 -d /nonexistent --daemon] - - assert_equal true, @cmd.options[:daemon] - assert_equal [File.expand_path('/nonexistent')], @cmd.options[:gemdir] - assert_equal 9999, @cmd.options[:port] - end - - def test_handle_options_gemdir - @cmd.send :handle_options, %w[--dir a --dir b] - - assert_equal [File.expand_path('a'), File.expand_path('b')], - @cmd.options[:gemdir] - end - - def test_handle_options_port - @cmd.send :handle_options, %w[-p 0] - assert_equal 0, @cmd.options[:port] - - @cmd.send :handle_options, %w[-p 65535] - assert_equal 65535, @cmd.options[:port] - - begin - @cmd.send :handle_options, %w[-p discard] - assert_equal 9, @cmd.options[:port] - rescue OptionParser::InvalidArgument - # for container environment on GitHub Actions + def test_execute + use_ui @ui do + @cmd.execute end - e = assert_raise OptionParser::InvalidArgument do - @cmd.send :handle_options, %w[-p nonexistent] - end - - assert_equal 'invalid argument: -p nonexistent: no such named service', - e.message - - e = assert_raise OptionParser::InvalidArgument do - @cmd.send :handle_options, %w[-p 65536] - end - - assert_equal 'invalid argument: -p 65536: not a port number', - e.message + assert_match %r{Install the rubygems-server}i, @ui.error end end diff --git a/test/rubygems/test_gem_server.rb b/test/rubygems/test_gem_server.rb deleted file mode 100644 index f6aa99fb02..0000000000 --- a/test/rubygems/test_gem_server.rb +++ /dev/null @@ -1,608 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'rubygems/server' -require 'stringio' - -class Gem::Server - attr_reader :server -end - -class TestGemServer < Gem::TestCase - def process_based_port - 0 - end - - def setup - super - - @a1 = quick_gem 'a', '1' - @a2 = quick_gem 'a', '2' - @a3_p = quick_gem 'a', '3.a' - - @server = Gem::Server.new Gem.dir, process_based_port, false - @req = WEBrick::HTTPRequest.new :Logger => nil - @res = WEBrick::HTTPResponse.new :HTTPVersion => '1.0' - end - - def test_doc_root_3 - orig_rdoc_version = Gem::RDoc.rdoc_version - Gem::RDoc.instance_variable_set :@rdoc_version, Gem::Version.new('3.12') - - assert_equal '/doc_root/X-1/rdoc/index.html', @server.doc_root('X-1') - - ensure - Gem::RDoc.instance_variable_set :@rdoc_version, orig_rdoc_version - end - - def test_doc_root_4 - orig_rdoc_version = Gem::RDoc.rdoc_version - Gem::RDoc.instance_variable_set :@rdoc_version, Gem::Version.new('4.0') - - assert_equal '/doc_root/X-1/', @server.doc_root('X-1') - - ensure - Gem::RDoc.instance_variable_set :@rdoc_version, orig_rdoc_version - end - - def test_have_rdoc_4_plus_eh - orig_rdoc_version = Gem::RDoc.rdoc_version - Gem::RDoc.instance_variable_set(:@rdoc_version, Gem::Version.new('4.0')) - - server = Gem::Server.new Gem.dir, 0, false - assert server.have_rdoc_4_plus? - - Gem::RDoc.instance_variable_set :@rdoc_version, Gem::Version.new('3.12') - - server = Gem::Server.new Gem.dir, 0, false - refute server.have_rdoc_4_plus? - - Gem::RDoc.instance_variable_set(:@rdoc_version, - Gem::Version.new('4.0.0.preview2')) - - server = Gem::Server.new Gem.dir, 0, false - assert server.have_rdoc_4_plus? - ensure - Gem::RDoc.instance_variable_set :@rdoc_version, orig_rdoc_version - end - - def test_spec_dirs - s = Gem::Server.new Gem.dir, process_based_port, false - - assert_equal [File.join(Gem.dir, 'specifications')], s.spec_dirs - - s = Gem::Server.new [Gem.dir, Gem.dir], process_based_port, false - - assert_equal [File.join(Gem.dir, 'specifications'), - File.join(Gem.dir, 'specifications')], s.spec_dirs - end - - def test_latest_specs - data = StringIO.new "GET /latest_specs.#{Gem.marshal_version} HTTP/1.0\r\n\r\n" - @req.parse data - - Gem::Deprecate.skip_during do - @server.latest_specs @req, @res - end - - assert_equal 200, @res.status, @res.body - assert_match %r{ \d\d:\d\d:\d\d }, @res['date'] - assert_equal 'application/octet-stream', @res['content-type'] - assert_equal [['a', Gem::Version.new(2), Gem::Platform::RUBY]], - Marshal.load(@res.body) - end - - def test_latest_specs_gemdirs - data = StringIO.new "GET /latest_specs.#{Gem.marshal_version} HTTP/1.0\r\n\r\n" - dir = "#{@gemhome}2" - - spec = util_spec 'z', 9 - - specs_dir = File.join dir, 'specifications' - FileUtils.mkdir_p specs_dir - - File.open File.join(specs_dir, spec.spec_name), 'w' do |io| - io.write spec.to_ruby - end - - server = Gem::Server.new dir, process_based_port, false - - @req.parse data - - server.latest_specs @req, @res - - assert_equal 200, @res.status - - assert_equal [['z', v(9), Gem::Platform::RUBY]], Marshal.load(@res.body) - end - - def test_latest_specs_gz - data = StringIO.new "GET /latest_specs.#{Gem.marshal_version}.gz HTTP/1.0\r\n\r\n" - @req.parse data - - Gem::Deprecate.skip_during do - @server.latest_specs @req, @res - end - - assert_equal 200, @res.status, @res.body - assert_match %r{ \d\d:\d\d:\d\d }, @res['date'] - assert_equal 'application/x-gzip', @res['content-type'] - assert_equal [['a', Gem::Version.new(2), Gem::Platform::RUBY]], - Marshal.load(Gem::Util.gunzip(@res.body)) - end - - def test_listen - util_listen - - capture_output do - @server.listen - end - - assert_equal 1, @server.server.listeners.length - end - - def test_listen_addresses - util_listen - - capture_output do - @server.listen %w[a b] - end - - assert_equal 2, @server.server.listeners.length - end - - def test_prerelease_specs - data = StringIO.new "GET /prerelease_specs.#{Gem.marshal_version} HTTP/1.0\r\n\r\n" - @req.parse data - - Gem::Deprecate.skip_during do - @server.prerelease_specs @req, @res - end - - assert_equal 200, @res.status, @res.body - assert_match %r{ \d\d:\d\d:\d\d }, @res['date'] - assert_equal 'application/octet-stream', @res['content-type'] - assert_equal [['a', v('3.a'), Gem::Platform::RUBY]], - Marshal.load(@res.body) - end - - def test_prerelease_specs_gz - data = StringIO.new "GET /prerelease_specs.#{Gem.marshal_version}.gz HTTP/1.0\r\n\r\n" - @req.parse data - - Gem::Deprecate.skip_during do - @server.prerelease_specs @req, @res - end - - assert_equal 200, @res.status, @res.body - assert_match %r{ \d\d:\d\d:\d\d }, @res['date'] - assert_equal 'application/x-gzip', @res['content-type'] - assert_equal [['a', v('3.a'), Gem::Platform::RUBY]], - Marshal.load(Gem::Util.gunzip(@res.body)) - end - - def test_quick_gemdirs - data = StringIO.new "GET /quick/Marshal.4.8/z-9.gemspec.rz HTTP/1.0\r\n\r\n" - dir = "#{@gemhome}2" - - server = Gem::Server.new dir, process_based_port, false - - @req.parse data - - server.quick @req, @res - - assert_equal 404, @res.status - - spec = util_spec 'z', 9 - - specs_dir = File.join dir, 'specifications' - - FileUtils.mkdir_p specs_dir - - File.open File.join(specs_dir, spec.spec_name), 'w' do |io| - io.write spec.to_ruby - end - - data.rewind - - req = WEBrick::HTTPRequest.new :Logger => nil - res = WEBrick::HTTPResponse.new :HTTPVersion => '1.0' - req.parse data - - server.quick req, res - - assert_equal 200, res.status - end - - def test_quick_missing - data = StringIO.new "GET /quick/Marshal.4.8/z-9.gemspec.rz HTTP/1.0\r\n\r\n" - @req.parse data - - @server.quick @req, @res - - assert_equal 404, @res.status, @res.body - assert_match %r{ \d\d:\d\d:\d\d }, @res['date'] - assert_equal 'text/plain', @res['content-type'] - assert_equal 'No gems found matching "z-9"', @res.body - assert_equal 404, @res.status - end - - def test_quick_marshal_a_1_gemspec_rz - data = StringIO.new "GET /quick/Marshal.#{Gem.marshal_version}/a-1.gemspec.rz HTTP/1.0\r\n\r\n" - @req.parse data - - @server.quick @req, @res - - assert_equal 200, @res.status, @res.body - assert @res['date'] - assert_equal 'application/x-deflate', @res['content-type'] - - spec = Marshal.load Gem::Util.inflate(@res.body) - assert_equal 'a', spec.name - assert_equal Gem::Version.new(1), spec.version - end - - def test_quick_marshal_a_1_mswin32_gemspec_rz - quick_gem 'a', '1' do |s| - s.platform = Gem::Platform.local - end - - data = StringIO.new "GET /quick/Marshal.#{Gem.marshal_version}/a-1-#{Gem::Platform.local}.gemspec.rz HTTP/1.0\r\n\r\n" - @req.parse data - - @server.quick @req, @res - - assert_equal 200, @res.status, @res.body - assert @res['date'] - assert_equal 'application/x-deflate', @res['content-type'] - - spec = Marshal.load Gem::Util.inflate(@res.body) - assert_equal 'a', spec.name - assert_equal Gem::Version.new(1), spec.version - assert_equal Gem::Platform.local, spec.platform - end - - def test_quick_marshal_a_3_a_gemspec_rz - data = StringIO.new "GET /quick/Marshal.#{Gem.marshal_version}/a-3.a.gemspec.rz HTTP/1.0\r\n\r\n" - @req.parse data - - @server.quick @req, @res - - assert_equal 200, @res.status, @res.body - assert @res['date'] - assert_equal 'application/x-deflate', @res['content-type'] - - spec = Marshal.load Gem::Util.inflate(@res.body) - assert_equal 'a', spec.name - assert_equal v('3.a'), spec.version - end - - def test_quick_marshal_a_b_3_a_gemspec_rz - quick_gem 'a-b', '3.a' - - data = StringIO.new "GET /quick/Marshal.#{Gem.marshal_version}/a-b-3.a.gemspec.rz HTTP/1.0\r\n\r\n" - @req.parse data - - @server.quick @req, @res - - assert_equal 200, @res.status, @res.body - assert @res['date'] - assert_equal 'application/x-deflate', @res['content-type'] - - spec = Marshal.load Gem::Util.inflate(@res.body) - assert_equal 'a-b', spec.name - assert_equal v('3.a'), spec.version - end - - def test_quick_marshal_a_b_1_3_a_gemspec_rz - quick_gem 'a-b-1', '3.a' - - data = StringIO.new "GET /quick/Marshal.#{Gem.marshal_version}/a-b-1-3.a.gemspec.rz HTTP/1.0\r\n\r\n" - @req.parse data - - @server.quick @req, @res - - assert_equal 200, @res.status, @res.body - assert @res['date'] - assert_equal 'application/x-deflate', @res['content-type'] - - spec = Marshal.load Gem::Util.inflate(@res.body) - assert_equal 'a-b-1', spec.name - assert_equal v('3.a'), spec.version - end - - def test_rdoc - data = StringIO.new "GET /rdoc?q=a HTTP/1.0\r\n\r\n" - @req.parse data - - @server.rdoc @req, @res - - assert_equal 200, @res.status, @res.body - assert_match %r{No documentation found}, @res.body - assert_equal 'text/html', @res['content-type'] - end - - def test_root - data = StringIO.new "GET / HTTP/1.0\r\n\r\n" - @req.parse data - - @server.root @req, @res - - assert_equal 200, @res.status, @res.body - assert_match %r{ \d\d:\d\d:\d\d }, @res['date'] - assert_equal 'text/html', @res['content-type'] - end - - def test_root_gemdirs - data = StringIO.new "GET / HTTP/1.0\r\n\r\n" - dir = "#{@gemhome}2" - - spec = util_spec 'z', 9 - - specs_dir = File.join dir, 'specifications' - FileUtils.mkdir_p specs_dir - - File.open File.join(specs_dir, spec.spec_name), 'w' do |io| - io.write spec.to_ruby - end - - server = Gem::Server.new dir, process_based_port, false - - @req.parse data - - server.root @req, @res - - assert_equal 200, @res.status - assert_match 'z 9', @res.body - end - - def test_xss_homepage_fix_289313 - data = StringIO.new "GET / HTTP/1.0\r\n\r\n" - dir = "#{@gemhome}2" - - spec = util_spec 'xsshomepagegem', 1 - spec.homepage = "javascript:confirm(document.domain)" - - specs_dir = File.join dir, 'specifications' - FileUtils.mkdir_p specs_dir - - File.open File.join(specs_dir, spec.spec_name), 'w' do |io| - io.write spec.to_ruby - end - - server = Gem::Server.new dir, process_based_port, false - - @req.parse data - - server.root @req, @res - - assert_equal 200, @res.status - assert_match 'xsshomepagegem 1', @res.body - - # This verifies that the homepage for this spec is not displayed and is set to ".", because it's not a - # valid HTTP/HTTPS URL and could be unsafe in an HTML context. We would prefer to throw an exception here, - # but spec.homepage is currently free form and not currently required to be a URL, this behavior may be - # validated in future versions of Gem::Specification. - # - # There are two variant we're checking here, one where rdoc is not present, and one where rdoc is present in the same regex: - # - # Variant #1 - rdoc not installed - # - # xsshomepagegem 1 - # - # - # [rdoc] - # - # - # - # [www] - # - # Variant #2 - rdoc installed - # - # xsshomepagegem 1 - # - # - # \[rdoc\]<\/a> - # - # - # - # [www] - regex_match = /xsshomepagegem 1<\/b>\s+(\[rdoc\]<\/span>|\[rdoc\]<\/a>)\s+\[www\]<\/a>/ - assert_match regex_match, @res.body - end - - def test_invalid_homepage - data = StringIO.new "GET / HTTP/1.0\r\n\r\n" - dir = "#{@gemhome}2" - - spec = util_spec 'invalidhomepagegem', 1 - spec.homepage = "notavalidhomepageurl" - - specs_dir = File.join dir, 'specifications' - FileUtils.mkdir_p specs_dir - - File.open File.join(specs_dir, spec.spec_name), 'w' do |io| - io.write spec.to_ruby - end - - server = Gem::Server.new dir, process_based_port, false - - @req.parse data - - server.root @req, @res - - assert_equal 200, @res.status - assert_match 'invalidhomepagegem 1', @res.body - - # This verifies that the homepage for this spec is not displayed and is set to ".", because it's not a - # valid HTTP/HTTPS URL and could be unsafe in an HTML context. We would prefer to throw an exception here, - # but spec.homepage is currently free form and not currently required to be a URL, this behavior may be - # validated in future versions of Gem::Specification. - # - # There are two variant we're checking here, one where rdoc is not present, and one where rdoc is present in the same regex: - # - # Variant #1 - rdoc not installed - # - # invalidhomepagegem 1 - # - # - # [rdoc] - # - # - # - # [www] - # - # Variant #2 - rdoc installed - # - # invalidhomepagegem 1 - # - # - # \[rdoc\]<\/a> - # - # - # - # [www] - regex_match = /invalidhomepagegem 1<\/b>\s+(\[rdoc\]<\/span>|\[rdoc\]<\/a>)\s+\[www\]<\/a>/ - assert_match regex_match, @res.body - end - - def test_valid_homepage_http - data = StringIO.new "GET / HTTP/1.0\r\n\r\n" - dir = "#{@gemhome}2" - - spec = util_spec 'validhomepagegemhttp', 1 - spec.homepage = "http://rubygems.org" - - specs_dir = File.join dir, 'specifications' - FileUtils.mkdir_p specs_dir - - File.open File.join(specs_dir, spec.spec_name), 'w' do |io| - io.write spec.to_ruby - end - - server = Gem::Server.new dir, process_based_port, false - - @req.parse data - - server.root @req, @res - - assert_equal 200, @res.status - assert_match 'validhomepagegemhttp 1', @res.body - - regex_match = /validhomepagegemhttp 1<\/b>\s+(\[rdoc\]<\/span>|\[rdoc\]<\/a>)\s+\[www\]<\/a>/ - assert_match regex_match, @res.body - end - - def test_valid_homepage_https - data = StringIO.new "GET / HTTP/1.0\r\n\r\n" - dir = "#{@gemhome}2" - - spec = util_spec 'validhomepagegemhttps', 1 - spec.homepage = "https://rubygems.org" - - specs_dir = File.join dir, 'specifications' - FileUtils.mkdir_p specs_dir - - File.open File.join(specs_dir, spec.spec_name), 'w' do |io| - io.write spec.to_ruby - end - - server = Gem::Server.new dir, process_based_port, false - - @req.parse data - - server.root @req, @res - - assert_equal 200, @res.status - assert_match 'validhomepagegemhttps 1', @res.body - - regex_match = /validhomepagegemhttps 1<\/b>\s+(\[rdoc\]<\/span>|\[rdoc\]<\/a>)\s+\[www\]<\/a>/ - assert_match regex_match, @res.body - end - - def test_specs - data = StringIO.new "GET /specs.#{Gem.marshal_version} HTTP/1.0\r\n\r\n" - @req.parse data - - @server.specs @req, @res - - assert_equal 200, @res.status, @res.body - assert_match %r{ \d\d:\d\d:\d\d }, @res['date'] - assert_equal 'application/octet-stream', @res['content-type'] - - assert_equal [['a', Gem::Version.new(1), Gem::Platform::RUBY], - ['a', Gem::Version.new(2), Gem::Platform::RUBY], - ['a', v('3.a'), Gem::Platform::RUBY]], - Marshal.load(@res.body) - end - - def test_specs_gemdirs - data = StringIO.new "GET /specs.#{Gem.marshal_version} HTTP/1.0\r\n\r\n" - dir = "#{@gemhome}2" - - spec = util_spec 'z', 9 - - specs_dir = File.join dir, 'specifications' - FileUtils.mkdir_p specs_dir - - File.open File.join(specs_dir, spec.spec_name), 'w' do |io| - io.write spec.to_ruby - end - - server = Gem::Server.new dir, process_based_port, false - - @req.parse data - - server.specs @req, @res - - assert_equal 200, @res.status - - assert_equal [['z', v(9), Gem::Platform::RUBY]], Marshal.load(@res.body) - end - - def test_specs_gz - data = StringIO.new "GET /specs.#{Gem.marshal_version}.gz HTTP/1.0\r\n\r\n" - @req.parse data - - @server.specs @req, @res - - assert_equal 200, @res.status, @res.body - assert_match %r{ \d\d:\d\d:\d\d }, @res['date'] - assert_equal 'application/x-gzip', @res['content-type'] - - assert_equal [['a', Gem::Version.new(1), Gem::Platform::RUBY], - ['a', Gem::Version.new(2), Gem::Platform::RUBY], - ['a', v('3.a'), Gem::Platform::RUBY]], - Marshal.load(Gem::Util.gunzip(@res.body)) - end - - def test_uri_encode - url_safe = @server.uri_encode 'http://rubyonrails.org/">malicious_content' - assert_equal url_safe, 'http://rubyonrails.org/%22%3Emalicious_content%3C/a%3E' - end - - # Regression test for issue #1793: incorrect URL encoding. - # Checking that no URLs have had '://' incorrectly encoded - def test_regression_1793 - data = StringIO.new "GET / HTTP/1.0\r\n\r\n" - @req.parse data - - @server.root @req, @res - - refute_match %r{%3A%2F%2F}, @res.body - end - - def util_listen - webrick = Object.new - webrick.instance_variable_set :@listeners, [] - def webrick.listeners() @listeners end - def webrick.listen(host, port) - socket = Object.new - socket.instance_variable_set :@host, host - socket.instance_variable_set :@port, port - def socket.addr() [nil, @port, @host] end - @listeners << socket - end - - @server.instance_variable_set :@server, webrick - end -end