Skip to content

Commit

Permalink
Merge #3017
Browse files Browse the repository at this point in the history
3017: Refactor remote fetcher r=hsbt a=deivid-rodriguez

# Description:

In rubygems/bundler#7460 I'm vendoring the `uri` library inside `bundler` to add support for using the `uri` version inside Gemfile's just like any other gem. However, that was not the only thing needed since `bundler/setup` also touches some code from this class in rubygems.

So the initial approach I took was to duplicate all the code inside bundler, replacing all reference to `URI` with `Bundler::URI` (the vendored namespace). Like in rubygems/bundler@3bcc579.

However, this is ugly and hard to maintain so in this PR I'm refactoring this class so that it is enough to override only one method in the `bundler` subclass, like this: https://github.com/bundler/bundler/blob/aa3a25d30383a16e002dd94c209a6078746204a7/lib/bundler/gem_remote_fetcher.rb 

# Tasks:

- [x] Describe the problem / feature
- [ ] Write tests
- [x] Write code to solve the problem
- [ ] Get code review from coworkers / friends

I will abide by the [code of conduct](https://github.com/rubygems/rubygems/blob/master/CODE_OF_CONDUCT.md).


Co-authored-by: David Rodríguez <deivid.rodriguez@riseup.net>
  • Loading branch information
2 people authored and hsbt committed Dec 13, 2019
1 parent 428672f commit 646b570
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 34 deletions.
2 changes: 2 additions & 0 deletions Manifest.txt
Expand Up @@ -495,6 +495,8 @@ lib/rubygems/test_utilities.rb
lib/rubygems/text.rb
lib/rubygems/uninstaller.rb
lib/rubygems/uri_formatter.rb
lib/rubygems/uri_parser.rb
lib/rubygems/uri_parsing.rb
lib/rubygems/user_interaction.rb
lib/rubygems/util.rb
lib/rubygems/util/licenses.rb
Expand Down
51 changes: 20 additions & 31 deletions lib/rubygems/remote_fetcher.rb
Expand Up @@ -4,6 +4,7 @@
require 'rubygems/request/connection_pools'
require 'rubygems/s3_uri_signer'
require 'rubygems/uri_formatter'
require 'rubygems/uri_parsing'
require 'rubygems/user_interaction'
require 'resolv'
require 'rubygems/deprecate'
Expand All @@ -17,26 +18,29 @@ class Gem::RemoteFetcher
include Gem::UserInteraction
extend Gem::Deprecate

include Gem::UriParsing

##
# A FetchError exception wraps up the various possible IO and HTTP failures
# that could happen while downloading from the internet.

class FetchError < Gem::Exception

include Gem::UriParsing

##
# The URI which was being accessed when the exception happened.

attr_accessor :uri

def initialize(message, uri)
super message
begin
uri = URI(uri)
uri.password = 'REDACTED' if uri.password
@uri = uri.to_s
rescue URI::InvalidURIError, ArgumentError
@uri = uri
end

uri = parse_uri(uri)

uri.password = 'REDACTED' if uri.respond_to?(:password) && uri.password

@uri = uri.to_s
end

def to_s # :nodoc:
Expand Down Expand Up @@ -107,7 +111,7 @@ def download_to_cache(dependency)

spec, source = found.max_by { |(s,_)| s.version }

download spec, source.uri.to_s
download spec, source.uri
end

##
Expand All @@ -130,18 +134,7 @@ def download(spec, source_uri, install_dir = Gem.dir)

FileUtils.mkdir_p cache_dir rescue nil unless File.exist? cache_dir

# Always escape URI's to deal with potential spaces and such
# It should also be considered that source_uri may already be
# a valid URI with escaped characters. e.g. "{DESede}" is encoded
# as "%7BDESede%7D". If this is escaped again the percentage
# symbols will be escaped.
unless source_uri.is_a?(URI::Generic)
begin
source_uri = URI.parse(source_uri)
rescue
source_uri = URI.parse(URI::DEFAULT_PARSER.escape(source_uri.to_s))
end
end
source_uri = parse_uri(source_uri)

scheme = source_uri.scheme

Expand All @@ -159,7 +152,7 @@ def download(spec, source_uri, install_dir = Gem.dir)
remote_gem_path = source_uri + "gems/#{gem_file_name}"

self.cache_update_path remote_gem_path, local_gem_path
rescue Gem::RemoteFetcher::FetchError
rescue FetchError
raise if spec.original_platform == spec.platform

alternate_name = "#{spec.original_name}.gem"
Expand Down Expand Up @@ -236,7 +229,7 @@ def fetch_http(uri, last_modified = nil, head = false, depth = 0)
unless location = response['Location']
raise FetchError.new("redirecting but no redirect location was given", uri)
end
location = URI.parse response['Location']
location = parse_uri location

if https?(uri) && !https?(location)
raise FetchError.new("redirecting to non-https resource: #{location}", uri)
Expand All @@ -254,9 +247,7 @@ def fetch_http(uri, last_modified = nil, head = false, depth = 0)
# Downloads +uri+ and returns it as a String.

def fetch_path(uri, mtime = nil, head = false)
uri = URI.parse uri unless URI::Generic === uri

raise ArgumentError, "bad uri: #{uri}" unless uri
uri = parse_uri uri

unless uri.scheme
raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}"
Expand All @@ -268,21 +259,19 @@ 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.to_s)
raise FetchError.new("server did not return a valid file", uri)
end
end

data
rescue FetchError
raise
rescue Timeout::Error
raise UnknownHostError.new('timed out', uri.to_s)
raise UnknownHostError.new('timed out', uri)
rescue IOError, SocketError, SystemCallError,
*(OpenSSL::SSL::SSLError if defined?(OpenSSL)) => e
if e.message =~ /getaddrinfo/
raise UnknownHostError.new('no such name', uri.to_s)
raise UnknownHostError.new('no such name', uri)
else
raise FetchError.new("#{e.class}: #{e}", uri.to_s)
raise FetchError.new("#{e.class}: #{e}", uri)
end
end

Expand Down
2 changes: 2 additions & 0 deletions lib/rubygems/request.rb
Expand Up @@ -19,6 +19,7 @@ def self.create_with_proxy(uri, request_class, last_modified, proxy) # :nodoc:
end

def self.proxy_uri(proxy) # :nodoc:
require "uri"
case proxy
when :no_proxy then nil
when URI::HTTP then proxy
Expand Down Expand Up @@ -173,6 +174,7 @@ def self.get_proxy_from_env(scheme = 'http')
:no_proxy : get_proxy_from_env('http')
end

require "uri"
uri = URI(Gem::UriFormatter.new(env_proxy).normalize)

if uri and uri.user.nil? and uri.password.nil?
Expand Down
1 change: 0 additions & 1 deletion lib/rubygems/source.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
autoload :FileUtils, 'fileutils'
autoload :URI, 'uri'

require "rubygems/text"
##
Expand Down
1 change: 0 additions & 1 deletion lib/rubygems/uri_formatter.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
require 'cgi'
require 'uri'

##
# The UriFormatter handles URIs from user-input and escaping.
Expand Down
36 changes: 36 additions & 0 deletions lib/rubygems/uri_parser.rb
@@ -0,0 +1,36 @@
# frozen_string_literal: true

##
# The UriParser handles parsing URIs.
#

class Gem::UriParser

##
# Parses the #uri, raising if it's invalid

def parse!(uri)
raise URI::InvalidURIError unless uri

# Always escape URI's to deal with potential spaces and such
# It should also be considered that source_uri may already be
# a valid URI with escaped characters. e.g. "{DESede}" is encoded
# as "%7BDESede%7D". If this is escaped again the percentage
# symbols will be escaped.
begin
URI.parse(uri)
rescue URI::InvalidURIError
URI.parse(URI::DEFAULT_PARSER.escape(uri))
end
end

##
# Parses the #uri, returning the original uri if it's invalid

def parse(uri)
parse!(uri)
rescue URI::InvalidURIError
uri
end

end
23 changes: 23 additions & 0 deletions lib/rubygems/uri_parsing.rb
@@ -0,0 +1,23 @@
# frozen_string_literal: true

require "rubygems/uri_parser"

module Gem::UriParsing

def parse_uri(source_uri)
return source_uri unless source_uri.is_a?(String)

uri_parser.parse(source_uri)
end

private :parse_uri

def uri_parser
require "uri"

Gem::UriParser.new
end

private :uri_parser

end
2 changes: 1 addition & 1 deletion test/rubygems/test_remote_fetch_error.rb
Expand Up @@ -5,7 +5,7 @@ class TestRemoteFetchError < Gem::TestCase

def test_password_redacted
error = Gem::RemoteFetcher::FetchError.new('There was an error fetching', 'https://user:secret@gemsource.org')
refute_match error.to_s, 'secret'
refute_match 'secret', error.to_s
end

def test_invalid_url
Expand Down

0 comments on commit 646b570

Please sign in to comment.