Handle invalid names in download strategies

When subformulae are initialized without a name parameter, Homebrew
assigns the name "__UNKNOWN__". This may cause collisions in the cache.

Currently CurlDownloadStrategy and its descendants handles this by
extracting the basename form the URL and using that as the cached
filename. However, other strategies simply raise an exception.

We can improve the other strategies by URL-encoding the URL string and
using that as the cached directory name.

Note that this happens very rarely, especially now that resources (which
always have a name) are preferred to subformulae. The most common case
is a subformula that specifies a head download.

Closes Homebrew/homebrew#22949.
This commit is contained in:
Jack Nagel 2013-09-30 21:36:38 -05:00
parent 48dde74503
commit 57560c03e6
2 changed files with 47 additions and 39 deletions

View File

@ -1,5 +1,6 @@
require 'open-uri' require 'open-uri'
require 'utils/json' require 'utils/json'
require 'erb'
class AbstractDownloadStrategy class AbstractDownloadStrategy
attr_reader :name, :resource attr_reader :name, :resource
@ -33,6 +34,14 @@ class AbstractDownloadStrategy
safe_system(*expand_safe_system_args(args)) safe_system(*expand_safe_system_args(args))
end end
def checkout_name(tag)
if name.empty? || name == '__UNKNOWN__'
"#{ERB::Util.url_encode(@url)}--#{tag}"
else
"#{name}--#{tag}"
end
end
# All download strategies are expected to implement these methods # All download strategies are expected to implement these methods
def fetch; end def fetch; end
def stage; end def stage; end
@ -298,13 +307,11 @@ class SubversionDownloadStrategy < AbstractDownloadStrategy
super super
@@svn ||= 'svn' @@svn ||= 'svn'
if name.to_s.empty? || name == '__UNKNOWN__' if ARGV.build_head?
raise NotImplementedError, "strategy requires a name parameter" @co = Pathname.new("#{HOMEBREW_CACHE}/#{checkout_name("svn-HEAD")}")
else else
@co = Pathname.new("#{HOMEBREW_CACHE}/#{name}--svn") @co = Pathname.new("#{HOMEBREW_CACHE}/#{checkout_name("svn")}")
end end
@co = Pathname.new(@co.to_s + '-HEAD') if ARGV.build_head?
end end
def cached_location def cached_location
@ -410,12 +417,7 @@ class GitDownloadStrategy < AbstractDownloadStrategy
def initialize name, resource def initialize name, resource
super super
@@git ||= 'git' @@git ||= 'git'
@clone = Pathname.new("#{HOMEBREW_CACHE}/#{checkout_name("git")}")
if name.to_s.empty? || name == '__UNKNOWN__'
raise NotImplementedError, "strategy requires a name parameter"
else
@clone = Pathname.new("#{HOMEBREW_CACHE}/#{name}--git")
end
end end
def cached_location def cached_location
@ -562,13 +564,7 @@ end
class CVSDownloadStrategy < AbstractDownloadStrategy class CVSDownloadStrategy < AbstractDownloadStrategy
def initialize name, resource def initialize name, resource
super super
@co = Pathname.new("#{HOMEBREW_CACHE}/#{checkout_name("cvs")}")
if name.to_s.empty? || name == '__UNKNOWN__'
raise NotImplementedError, "strategy requires a name parameter"
else
@unique_token = "#{name}--cvs"
@co = Pathname.new("#{HOMEBREW_CACHE}/#{@unique_token}")
end
end end
def cached_location; @co; end def cached_location; @co; end
@ -585,7 +581,7 @@ class CVSDownloadStrategy < AbstractDownloadStrategy
unless @co.exist? unless @co.exist?
Dir.chdir HOMEBREW_CACHE do Dir.chdir HOMEBREW_CACHE do
safe_system '/usr/bin/cvs', '-d', url, 'login' safe_system '/usr/bin/cvs', '-d', url, 'login'
safe_system '/usr/bin/cvs', '-d', url, 'checkout', '-d', @unique_token, mod safe_system '/usr/bin/cvs', '-d', url, 'checkout', '-d', checkout_name("cvs"), mod
end end
else else
puts "Updating #{@co}" puts "Updating #{@co}"
@ -618,12 +614,7 @@ end
class MercurialDownloadStrategy < AbstractDownloadStrategy class MercurialDownloadStrategy < AbstractDownloadStrategy
def initialize name, resource def initialize name, resource
super super
@clone = Pathname.new("#{HOMEBREW_CACHE}/#{checkout_name("hg")}")
if name.to_s.empty? || name == '__UNKNOWN__'
raise NotImplementedError, "strategy requires a name parameter"
else
@clone = Pathname.new("#{HOMEBREW_CACHE}/#{name}--hg")
end
end end
def cached_location; @clone; end def cached_location; @clone; end
@ -678,12 +669,7 @@ end
class BazaarDownloadStrategy < AbstractDownloadStrategy class BazaarDownloadStrategy < AbstractDownloadStrategy
def initialize name, resource def initialize name, resource
super super
@clone = Pathname.new("#{HOMEBREW_CACHE}/#{checkout_name("bzr")}")
if name.to_s.empty? || name == '__UNKNOWN__'
raise NotImplementedError, "strategy requires a name parameter"
else
@clone = Pathname.new("#{HOMEBREW_CACHE}/#{name}--bzr")
end
end end
def cached_location; @clone; end def cached_location; @clone; end
@ -731,11 +717,7 @@ end
class FossilDownloadStrategy < AbstractDownloadStrategy class FossilDownloadStrategy < AbstractDownloadStrategy
def initialize name, resource def initialize name, resource
super super
if name.to_s.empty? || name == '__UNKNOWN__' @clone = Pathname.new("#{HOMEBREW_CACHE}/#{checkout_name("fossil")}")
raise NotImplementedError, "strategy requires a name parameter"
else
@clone = Pathname.new("#{HOMEBREW_CACHE}/#{name}--fossil")
end
end end
def cached_location; @clone; end def cached_location; @clone; end

View File

@ -2,7 +2,7 @@ require 'testing_env'
require 'download_strategy' require 'download_strategy'
require 'bottles' # XXX: hoist these regexps into constants in Pathname? require 'bottles' # XXX: hoist these regexps into constants in Pathname?
class SoftwareSpecDouble class ResourceDouble
attr_reader :url, :specs attr_reader :url, :specs
def initialize(url="http://foo.com/bar.tar.gz", specs={}) def initialize(url="http://foo.com/bar.tar.gz", specs={})
@ -14,8 +14,8 @@ end
class AbstractDownloadStrategyTests < Test::Unit::TestCase class AbstractDownloadStrategyTests < Test::Unit::TestCase
def setup def setup
@name = "foo" @name = "foo"
@package = SoftwareSpecDouble.new @resource = ResourceDouble.new
@strategy = AbstractDownloadStrategy.new(@name, @package) @strategy = AbstractDownloadStrategy.new(@name, @resource)
@args = %w{foo bar baz} @args = %w{foo bar baz}
end end
@ -37,6 +37,32 @@ class AbstractDownloadStrategyTests < Test::Unit::TestCase
end end
end end
class DownloadStrategyCheckoutNameTests < Test::Unit::TestCase
def setup
@resource = ResourceDouble.new("http://foo.com/bar")
@strategy = AbstractDownloadStrategy
end
def escaped(tag)
"#{ERB::Util.url_encode(@resource.url)}--#{tag}"
end
def test_explicit_name
downloader = @strategy.new("baz", @resource)
assert_equal "baz--foo", downloader.checkout_name("foo")
end
def test_empty_name
downloader = @strategy.new("", @resource)
assert_equal escaped("foo"), downloader.checkout_name("foo")
end
def test_unknown_name
downloader = @strategy.new("__UNKNOWN__", @resource)
assert_equal escaped("foo"), downloader.checkout_name("foo")
end
end
class DownloadStrategyDetectorTests < Test::Unit::TestCase class DownloadStrategyDetectorTests < Test::Unit::TestCase
def setup def setup
@d = DownloadStrategyDetector.new @d = DownloadStrategyDetector.new