Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -1050,8 +1050,8 @@ $(ENC_MK): $(srcdir)/enc/make_encmake.rb $(srcdir)/enc/Makefile.in $(srcdir)/enc

PHONY:

{$(VPATH)}parse.c: {$(VPATH)}parse.y {$(VPATH)}id.h
{$(VPATH)}parse.h: {$(VPATH)}parse.c
parse.c: {$(VPATH)}parse.y {$(VPATH)}id.h
parse.h: {$(VPATH)}parse.c

{$(srcdir)}.y.c:
$(ECHO) generating $@
Expand Down Expand Up @@ -1310,9 +1310,7 @@ $(MAINOBJ): $(srcdir)/$(MAINSRC)
$(ECHO) compiling $(srcdir)/$(MAINSRC)
$(Q) $(CC) $(MAINCPPFLAGS) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$(srcdir)/$(MAINSRC)

{$(VPATH)}probes.dmyh: {$(srcdir)}probes.d $(tooldir)/gen_dummy_probes.rb

probes.dmyh:
probes.dmyh: {$(srcdir)}probes.d $(tooldir)/gen_dummy_probes.rb
$(BASERUBY) $(tooldir)/gen_dummy_probes.rb $(srcdir)/probes.d > $@

probes.h: {$(VPATH)}probes.$(DTRACE_EXT) $(srcdir)/vm_opts.h
Expand Down
10 changes: 5 additions & 5 deletions file.c
Original file line number Diff line number Diff line change
Expand Up @@ -6038,14 +6038,14 @@ rb_file_join(long argc, VALUE *args)
}
/*
* call-seq:
* File.join(*objects) -> new_string
* File.join(*components) -> string
*
* Returns a new string formed by joining the given string-converted +objects+
* Returns a new string formed by joining the given string +components+
* with character <tt>'/'</tt>:
*
* File.join # => ""
* File.join('foo') # => "foo"
* File.join('foo', 'bar', 'baz') # => "foo/bar/baz"
* File.join # => ""
* File.join('foo') # => "foo"
* File.join(*%w[bar baz bat]) # => "bar/baz/bat"
*
*/

Expand Down
13 changes: 8 additions & 5 deletions lib/bundler/bundler.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ Gem::Specification.new do |s|
s.version = Bundler::VERSION
s.license = "MIT"
s.authors = [
"André Arko", "Samuel Giddins", "Colby Swandale", "Hiroshi Shibata",
"David Rodríguez", "Grey Baker", "Stephanie Morillo", "Chris Morris", "James Wen", "Tim Moore",
"André Medeiros", "Jessica Lynn Suttles", "Terence Lee", "Carl Lerche",
"Yehuda Katz"
"Yehuda Katz", "Carl Lerche", "André Arko", "Terence Lee", "Tim Moore",
"Jessica Lynn Suttles", "Hiroshi SHIBATA", "André Medeiros", "Samuel Giddins", "David Rodríguez",
"James Wen", "Chris Morris", "Colby Swandale", "Grey Baker", "Stephanie Morillo"
]
s.email = [
"wycats@gmail.com", "me@carllerche.com", "andre@arko.net", "hone02@gmail.com", "tmoore@incrementalism.net",
"jlsuttles@gmail.com", "hsbt@ruby-lang.org", "me@andremedeiros.info", "segiddins@segiddins.me", "deivid.rodriguez@riseup.net",
"jrw2175@columbia.edu", "chrismo@clabs.org", "colby@rubygems.org", "greysteil@gmail.com", ""
]
s.email = ["team@bundler.io"]
s.homepage = "https://bundler.io"
s.summary = "The best way to manage your application's dependencies"
s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably"
Expand Down
9 changes: 7 additions & 2 deletions lib/bundler/endpoint_specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,16 @@ def parse_metadata(data)
TIME_ZONE_SUFFIX = /(?:Z|z|[+-]\d{2}(?::?\d{2})?)\z/
private_constant :TIME_ZONE_SUFFIX

# See Gem::Cooldown::FOUR_DIGIT_YEAR.
FOUR_DIGIT_YEAR = /\A\d{4}-/
private_constant :FOUR_DIGIT_YEAR

# A timestamp without a time zone offset is read as UTC, because reading
# it as local time would shift the cooldown window by the environment's
# offset. Unparsable values become nil so the cooldown fails open.
# offset. Unparsable values and years outside four digits become nil so
# the cooldown fails open.
def parse_created_at(value)
return unless value.is_a?(String)
return unless value.is_a?(String) && value.match?(FOUR_DIGIT_YEAR)

require "time"
begin
Expand Down
10 changes: 6 additions & 4 deletions lib/bundler/source/git.rb
Original file line number Diff line number Diff line change
Expand Up @@ -432,10 +432,12 @@ def fetch
def validate_spec(_spec); end

def load_gemspec(file)
dirname = Pathname.new(file).dirname
SharedHelpers.chdir(dirname.to_s) do
stub = Gem::StubSpecification.gemspec_stub(file, install_path.parent, install_path.parent)
stub.full_gem_path = dirname.expand_path(root).to_s
# Expand the path before the chdir below, since resolving it inside the
# block would base it on the gemspec directory instead of `root`.
gemspec_path = Pathname.new(file).expand_path(root)
SharedHelpers.chdir(gemspec_path.dirname.to_s) do
stub = Gem::StubSpecification.gemspec_stub(gemspec_path.to_s, install_path.parent, install_path.parent)
stub.full_gem_path = gemspec_path.dirname.to_s
StubSpecification.from_stub(stub)
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/source/git/git_proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ def verify(reference)
def configured_uri
@configured_uri ||= if /https?:/.match?(uri)
remote = Gem::URI(uri)
config_auth = Bundler.settings[remote.to_s] || Bundler.settings[remote.host]
config_auth = Bundler.settings.credentials_for(remote)
remote.userinfo ||= config_auth
remote.to_s
else
Expand Down
2 changes: 1 addition & 1 deletion lib/rubygems/compact_index_client/cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def info_etag_path(name)
# validated, so refuse anything that would escape the cache
# directory when used as a path component.
def validate_name!(name)
return if File.basename(name) == name
return unless name.empty? || name.include?("\0") || name == "." || name == ".." || File.basename(name) != name

raise Gem::Exception, "malformed gem name: #{name.inspect}"
end
Expand Down
26 changes: 21 additions & 5 deletions lib/rubygems/compact_index_client/http_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ def fetch(uri, headers, redirects_remaining)
response = request(uri, headers)

case response
when Gem::Net::HTTPSuccess, Gem::Net::HTTPNotModified
when Gem::Net::HTTPNotModified
response
when Gem::Net::HTTPSuccess
# The callers write the body into the cache, so a body-less success
# such as 204 would truncate the cached file.
raise bad_response(response, uri) unless response.class.body_permitted?

response
when Gem::Net::HTTPMovedPermanently, Gem::Net::HTTPFound, Gem::Net::HTTPSeeOther,
Gem::Net::HTTPTemporaryRedirect, Gem::Net::HTTPPermanentRedirect
Expand All @@ -42,15 +48,20 @@ def fetch(uri, headers, redirects_remaining)
if https?(uri) && !https?(redirect)
raise Gem::RemoteFetcher::FetchError.new("redirecting to non-https resource: #{Gem::Uri.redact(redirect)}", uri)
end
# An absolute Location on the same host drops the credentials that a
# relative one would have kept.
redirect.userinfo = uri.userinfo if redirect.host == uri.host && !redirect.userinfo

fetch(redirect, headers, redirects_remaining - 1)
when Gem::Net::HTTPRangeNotSatisfiable
raise Gem::RemoteFetcher::FetchError.new("bad response #{response.message} #{response.code}", uri) unless headers.key?("Range")
raise bad_response(response, uri) unless headers.key?("Range")

# The local cache is longer than the remote file, refetch it whole.
fetch(uri, headers.except("Range"), redirects_remaining)
# The local cache is longer than the remote file, refetch it whole. A
# matching ETag would otherwise turn the retry into a 304 and keep the
# oversized cache.
fetch(uri, headers.except("Range", "If-None-Match"), redirects_remaining)
else
raise Gem::RemoteFetcher::FetchError.new("bad response #{response.message} #{response.code}", uri)
raise bad_response(response, uri)
end
end

Expand All @@ -65,6 +76,11 @@ def request(uri, headers)
raise Gem::RemoteFetcher::FetchError.new("#{e.class}: #{e}", uri)
end

def bad_response(response, uri)
detail = response["X-Error-Message"] || response.message
Gem::RemoteFetcher::FetchError.new("bad response #{detail} #{response.code}", uri)
end

def https?(uri)
uri.scheme == "https"
end
Expand Down
11 changes: 9 additions & 2 deletions lib/rubygems/cooldown.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,21 @@ def self.output_skipped_summary(entries)
TIME_ZONE_SUFFIX = /(?:Z|z|[+-]\d{2}(?::?\d{2})?)\z/ # :nodoc:
private_constant :TIME_ZONE_SUFFIX

# Matches the four-digit year an ISO 8601 timestamp starts with.
# Time.iso8601 also accepts a year of any length, and one far enough
# away overflows the Float arithmetic behind #remaining_days.
FOUR_DIGIT_YEAR = /\A\d{4}-/ # :nodoc:
private_constant :FOUR_DIGIT_YEAR

##
# Parses a +created_at+ timestamp from the compact index. A timestamp
# without a time zone offset is read as UTC, because reading it as local
# time would shift the cooldown window by the environment's offset.
# Returns nil for anything unparsable, so the cooldown fails open.
# Returns nil for anything unparsable, including a year outside four
# digits, so the cooldown fails open.

def self.parse_created_at(value)
return unless value.is_a?(String)
return unless value.is_a?(String) && value.match?(FOUR_DIGIT_YEAR)

require "time"
begin
Expand Down
21 changes: 15 additions & 6 deletions pathname_builtin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -955,16 +955,25 @@ def plus(path1, path2) # :nodoc:
end
private :plus

# :markup: markdown
#
# call-seq:
# join(*objects) -> new_pathname
# join(*components) -> self or new_pathname
#
# With no arguments, returns `self`.
#
# Joins the string-converted given +objects+ to the string path in +self+;
# With arguments, joins the given `components` to the string path in `self`
# with character `'/'`;
# returns a new pathname containing the joined string:
#
# Pathname('foo').join # => #<Pathname:foo>
# Pathname('foo').join('bar') # => #<Pathname:foo/bar>
# Pathname('foo').join('bar', 'baz') # => #<Pathname:foo/bar/baz>
# Pathname('foo').join(Pathname('bar')) # => #<Pathname:foo/bar>
# ```ruby
# pn = Pathname('foo') # => #<Pathname:foo>
# # String arguments.
# pn.join('bar') # => #<Pathname:foo/bar>
# pn.join(*%w[bar baz bat]) # => #<Pathname:foo/bar/baz/bat>
# # Pathname arguments.
# pn.join(Pathname('bar'), Pathname('baz')) # => #<Pathname:foo/bar/baz>
# ```
#
def join(*args)
return self if args.empty?
Expand Down
8 changes: 8 additions & 0 deletions spec/bundler/bundler/endpoint_specification_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ def with_tz(tz)
end
end

context "when created_at has a year that overflows Float arithmetic" do
let(:metadata) { { "created_at" => ["#{"9" * 400}-01-01T00:00:00Z"] } }

it "leaves created_at as nil" do
expect(subject.created_at).to be_nil
end
end

context "when the metadata has an empty checksum value" do
let(:metadata) { { "checksum" => [] } }

Expand Down
10 changes: 10 additions & 0 deletions spec/bundler/bundler/resolver/cooldown_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ def spec(created_at:, remote:, name: "myrack", version: "1.0.0")
end
end

context "when created_at has a year that overflows Float arithmetic" do
it "keeps the spec like one without created_at" do
metadata = { "created_at" => ["#{"9" * 400}-01-01T00:00:00Z"] }
s = Bundler::EndpointSpecification.new("myrack", "1.0.0", Gem::Platform::RUBY, nil, [], metadata)
s.remote = remote(cooldown: 7)

expect(resolver.send(:filter_cooldown, [s])).to eq([s])
end
end

context "when the remote has no cooldown" do
it "keeps every spec" do
s = spec(created_at: now - 3600, remote: remote(cooldown: nil))
Expand Down
33 changes: 33 additions & 0 deletions spec/bundler/bundler/source/git/git_proxy_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# frozen_string_literal: true

require "rubygems/credential_store"
require_relative "../../../support/fake_credential_backend"

RSpec.describe Bundler::Source::Git::GitProxy do
let(:path) { Pathname("path") }
let(:uri) { "https://github.com/ruby/rubygems.git" }
Expand Down Expand Up @@ -98,6 +101,36 @@
end
end

context "with credentials in the credential store" do
let(:fake_store) { Gem::CredentialStore.new(backend: FakeCredentialBackend.new) }

before do
Gem::CredentialStore.instance = fake_store
fake_store.set(Bundler::Settings.key_for("github.com"), "u:p")
end

after { Gem::CredentialStore.reset! }

it "adds username and password from the store to URI for host" do
Bundler.settings.temporary("credential_store" => "true") do
expect(Bundler.settings["github.com"]).to be_nil
allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0")
expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", "https://u:p@github.com/ruby/rubygems.git", path.to_s], nil).and_return(["", "", clone_result])
subject.checkout
end
end

it "keeps original userinfo" do
Bundler.settings.temporary("credential_store" => "true") do
original = "https://orig:info@github.com/ruby/rubygems.git"
git_proxy = described_class.new(Pathname("path"), original, options)
allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0")
expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", original, path.to_s], nil).and_return(["", "", clone_result])
git_proxy.checkout
end
end
end

describe "filtering credentials out of command output" do
let(:secret) { "s3cr3tp4ss" }
let(:credentialed_uri) { "https://user:#{secret}@github.com/ruby/rubygems.git" }
Expand Down
26 changes: 26 additions & 0 deletions spec/bundler/bundler/source/git_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,30 @@
expect(::Bundler::FileUtils).to have_received(:rm_rf).once
end
end

describe "#load_gemspec" do
let(:options) do
{ "uri" => uri, "revision" => "123abc" }
end

before do
allow(Bundler).to receive(:root).and_return(tmp)
allow(subject).to receive(:install_path).and_return(tmp("install/bar-123abc"))

create_file(tmp("bar/bar.gemspec"), <<~GEMSPEC)
Gem::Specification.new do |s|
s.name = "bar"
s.version = "1.0"
end
GEMSPEC
end

it "resolves a relative path against the root, not the gemspec directory" do
spec = Dir.chdir(tmp) { subject.send(:load_gemspec, "bar/bar.gemspec") }

expect(spec.name).to eq("bar")
expect(spec.loaded_from).to eq(tmp("bar/bar.gemspec").to_s)
expect(spec.full_gem_path).to eq(tmp("bar").to_s)
end
end
end
16 changes: 16 additions & 0 deletions spec/bundler/install/cooldown_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,22 @@
expect(the_bundle).to include_gems("ripe_gem 2.0.0")
end

it "treats a created_at with a year that overflows Float arithmetic as unknown" do
gemfile <<-G
source "https://gem.repo3"
gem "ripe_gem"
G

bundle "install --cooldown 7", artifice: "compact_index_cooldown_bad_created_at"

expect(the_bundle).to include_gems("ripe_gem 2.0.0")
expect(out).not_to include("skipped by the cooldown setting")

bundle "outdated --cooldown 7", artifice: "compact_index_cooldown_bad_created_at", raise_on_error: false

expect(out).not_to include("cooldown")
end

it "annotates in-cooldown versions in bundle outdated table output" do
gemfile <<-G
source "https://gem.repo3"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

require_relative "helpers/compact_index_cooldown"

# Serves every version with a created_at year that Time.iso8601 accepts but
# whose distance from now overflows Float.
class CompactIndexCooldownBadCreatedAt < CompactIndexCooldownAPI
helpers do
def build_gem_version(spec, deps, checksum)
CompactIndex::GemVersionV2.new(spec.version.version, spec.platform.to_s, checksum, nil,
deps, spec.required_ruby_version.to_s, spec.required_rubygems_version.to_s, "#{"9" * 400}-01-01T00:00:00Z")
end
end
end

require_relative "helpers/artifice"

Artifice.activate_with(CompactIndexCooldownBadCreatedAt)
Loading