Skip to content

Commit

Permalink
Refactor Gem::RemoteFetcher::FetchError initializer to build method
Browse files Browse the repository at this point in the history
The `initialize` method is already doing a lot and by adding the `Gem::PrintableUri` to redact sensitive information, things are getting complicated and hard to read here. For the start, I have refactored the `initialize` method into a class method called `build`.
  • Loading branch information
daniel-niknam authored and deivid-rodriguez committed Aug 24, 2021
1 parent f566787 commit 4312e8f
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 30 deletions.
36 changes: 21 additions & 15 deletions lib/rubygems/remote_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
require_relative 's3_uri_signer'
require_relative 'uri_formatter'
require_relative 'uri_parser'
require_relative 'printable_uri'
require_relative 'user_interaction'

##
Expand All @@ -21,19 +22,24 @@ class Gem::RemoteFetcher
class FetchError < Gem::Exception
##
# The URI which was being accessed when the exception happened.
def self.build(message, uri)
original_uri = uri.dup
uri = Gem::PrintableUri.parse_uri(uri)

attr_accessor :uri, :original_uri

def initialize(message, uri)
super message
if uri.respond_to?(:original_password) && uri.original_password
message = message.sub(uri.original_password, 'REDACTED')
end

uri = Gem::UriParser.parse_uri(uri)
new(message, uri.to_s, original_uri)
end

@original_uri = uri.dup
attr_accessor :uri, :original_uri

uri.password = 'REDACTED' if uri.respond_to?(:password) && uri.password
def initialize(message, uri, original_uri = nil)
super message

@uri = uri.to_s
@uri = uri
@original_uri = original_uri ? original_uri : uri
end

def to_s # :nodoc:
Expand Down Expand Up @@ -219,20 +225,20 @@ def fetch_http(uri, last_modified = nil, head = false, depth = 0)
head ? response : response.body
when Net::HTTPMovedPermanently, Net::HTTPFound, Net::HTTPSeeOther,
Net::HTTPTemporaryRedirect then
raise FetchError.new('too many redirects', uri) if depth > 10
raise FetchError.build('too many redirects', uri) if depth > 10

unless location = response['Location']
raise FetchError.new("redirecting but no redirect location was given", uri)
raise FetchError.build("redirecting but no redirect location was given", uri)
end
location = Gem::UriParser.parse_uri location

if https?(uri) && !https?(location)
raise FetchError.new("redirecting to non-https resource: #{location}", uri)
raise FetchError.build("redirecting to non-https resource: #{location}", uri)
end

fetch_http(location, last_modified, head, depth + 1)
else
raise FetchError.new("bad response #{response.message} #{response.code}", uri)
raise FetchError.build("bad response #{response.message} #{response.code}", uri)
end
end

Expand All @@ -254,21 +260,21 @@ def fetch_path(uri, mtime = nil, head = false)
begin
data = Gem::Util.gunzip data
rescue Zlib::GzipFile::Error
raise FetchError.new("server did not return a valid file", uri)
raise FetchError.build("server did not return a valid file", uri)
end
end

data
rescue Timeout::Error, IOError, SocketError, SystemCallError,
*(OpenSSL::SSL::SSLError if Gem::HAVE_OPENSSL) => e
raise FetchError.new("#{e.class}: #{e}", uri)
raise FetchError.build("#{e.class}: #{e}", uri)
end

def fetch_s3(uri, mtime = nil, head = false)
begin
public_uri = s3_uri_signer(uri).sign
rescue Gem::S3URISigner::ConfigurationError, Gem::S3URISigner::InstanceProfileError => e
raise FetchError.new(e.message, "s3://#{uri.host}")
raise FetchError.build(e.message, "s3://#{uri.host}")
end
fetch_https public_uri, mtime, head
end
Expand Down
8 changes: 4 additions & 4 deletions lib/rubygems/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def connection_for(uri)
@connection_pool.checkout
rescue Gem::HAVE_OPENSSL ? OpenSSL::SSL::SSLError : Errno::EHOSTDOWN,
Errno::EHOSTDOWN => e
raise Gem::RemoteFetcher::FetchError.new(e.message, uri)
raise Gem::RemoteFetcher::FetchError.build(e.message, uri)
end

def fetch
Expand Down Expand Up @@ -228,14 +228,14 @@ def perform_request(request) # :nodoc:

reset connection

raise Gem::RemoteFetcher::FetchError.new('too many bad responses', @uri) if bad_response
raise Gem::RemoteFetcher::FetchError.build('too many bad responses', @uri) if bad_response

bad_response = true
retry
rescue Net::HTTPFatalError
verbose "fatal error"

raise Gem::RemoteFetcher::FetchError.new('fatal error', @uri)
raise Gem::RemoteFetcher::FetchError.build('fatal error', @uri)
# HACK work around EOFError bug in Net::HTTP
# NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible
# to install gems.
Expand All @@ -245,7 +245,7 @@ def perform_request(request) # :nodoc:
requests = @requests[connection.object_id]
verbose "connection reset after #{requests} requests, retrying"

raise Gem::RemoteFetcher::FetchError.new('too many connection resets', @uri) if retried
raise Gem::RemoteFetcher::FetchError.build('too many connection resets', @uri) if retried

reset connection

Expand Down
2 changes: 1 addition & 1 deletion test/rubygems/test_gem_commands_sources_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ def test_execute_add_nonexistent_source

uri = "http://beta-gems.example.com/specs.#{@marshal_version}.gz"
@fetcher.data[uri] = proc do
raise Gem::RemoteFetcher::FetchError.new('it died', uri)
raise Gem::RemoteFetcher::FetchError.build('it died', uri)
end

@cmd.handle_options %w[--add http://beta-gems.example.com]
Expand Down
32 changes: 31 additions & 1 deletion test/rubygems/test_gem_remote_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ def self.fetch_path(arg, *rest)
@test_data
end

raise Gem::RemoteFetcher::FetchError.new("haha!", '')
raise Gem::RemoteFetcher::FetchError.build("haha!", '')
end
end

Expand Down Expand Up @@ -241,6 +241,36 @@ def test_download_with_auth
assert File.exist?(a1_cache_gem)
end

def test_download_with_token
a1_data = nil
File.open @a1_gem, 'rb' do |fp|
a1_data = fp.read
end

fetcher = util_fuck_with_fetcher a1_data

a1_cache_gem = @a1.cache_file
assert_equal a1_cache_gem, fetcher.download(@a1, 'http://token@gems.example.com')
assert_equal("http://token@gems.example.com/gems/a-1.gem",
fetcher.instance_variable_get(:@test_arg).to_s)
assert File.exist?(a1_cache_gem)
end

def test_download_with_x_oauth_basic
a1_data = nil
File.open @a1_gem, 'rb' do |fp|
a1_data = fp.read
end

fetcher = util_fuck_with_fetcher a1_data

a1_cache_gem = @a1.cache_file
assert_equal a1_cache_gem, fetcher.download(@a1, 'http://token:x-oauth-basic@gems.example.com')
assert_equal("http://token:x-oauth-basic@gems.example.com/gems/a-1.gem",
fetcher.instance_variable_get(:@test_arg).to_s)
assert File.exist?(a1_cache_gem)
end

def test_download_with_encoded_auth
a1_data = nil
File.open @a1_gem, 'rb' do |fp|
Expand Down
6 changes: 3 additions & 3 deletions test/rubygems/test_gem_resolver_best_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def test_replace_failed_api_set

error_uri = api_uri + 'a'

error = Gem::RemoteFetcher::FetchError.new 'bogus', error_uri
error = Gem::RemoteFetcher::FetchError.build 'bogus', error_uri

set.replace_failed_api_set error

Expand All @@ -124,7 +124,7 @@ def test_replace_failed_api_set_no_api_set

set.sets << index_set

error = Gem::RemoteFetcher::FetchError.new 'bogus', @gem_repo
error = Gem::RemoteFetcher::FetchError.build 'bogus', @gem_repo

e = assert_raise Gem::RemoteFetcher::FetchError do
set.replace_failed_api_set error
Expand All @@ -145,7 +145,7 @@ def test_replace_failed_api_set_uri_with_credentials

error_uri = api_uri + 'a'

error = Gem::RemoteFetcher::FetchError.new 'bogus', error_uri
error = Gem::RemoteFetcher::FetchError.build 'bogus', error_uri

set.replace_failed_api_set error

Expand Down
2 changes: 1 addition & 1 deletion test/rubygems/test_gem_spec_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def test_spec_for_dependency_mismatched_platform
def test_spec_for_dependency_bad_fetch_spec
src = Gem::Source.new(@gem_repo)
def src.fetch_spec(name)
raise Gem::RemoteFetcher::FetchError.new("bad news from the internet", @uri)
raise Gem::RemoteFetcher::FetchError.build("bad news from the internet", @uri)
end

Gem.sources.replace [src]
Expand Down
6 changes: 3 additions & 3 deletions test/rubygems/test_remote_fetch_error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@

class TestRemoteFetchError < Gem::TestCase
def test_password_redacted
error = Gem::RemoteFetcher::FetchError.new('There was an error fetching', 'https://user:secret@gemsource.org')
error = Gem::RemoteFetcher::FetchError.build('There was an error fetching', 'https://user:secret@gemsource.org')
refute_match %r{secret}, error.to_s
end

def test_invalid_url
error = Gem::RemoteFetcher::FetchError.new('There was an error fetching', 'https://::gemsource.org')
error = Gem::RemoteFetcher::FetchError.build('There was an error fetching', 'https://::gemsource.org')
assert_equal error.to_s, 'There was an error fetching (https://::gemsource.org)'
end

def test_to_s
error = Gem::RemoteFetcher::FetchError.new('There was an error fetching', 'https://gemsource.org')
error = Gem::RemoteFetcher::FetchError.build('There was an error fetching', 'https://gemsource.org')
assert_equal error.to_s, 'There was an error fetching (https://gemsource.org)'
end
end
4 changes: 2 additions & 2 deletions test/rubygems/utilities.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def find_data(path)
raise ArgumentError, 'need full URI' unless path.start_with?("https://", "http://")

unless @data.key? path
raise Gem::RemoteFetcher::FetchError.new("no data for #{path}", path)
raise Gem::RemoteFetcher::FetchError.build("no data for #{path}", path)
end

if @data[path].kind_of?(Array) && @data[path].first.kind_of?(Array)
Expand Down Expand Up @@ -124,7 +124,7 @@ def fetch_size(path)
raise ArgumentError, 'need full URI' unless path =~ %r{^http://}

unless @data.key? path
raise Gem::RemoteFetcher::FetchError.new("no data for #{path}", path)
raise Gem::RemoteFetcher::FetchError.build("no data for #{path}", path)
end

data = @data[path]
Expand Down

0 comments on commit 4312e8f

Please sign in to comment.