diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/bundler/resolver.rb | 16 | ||||
| -rw-r--r-- | lib/rubygems/installer.rb | 19 | ||||
| -rw-r--r-- | lib/rubygems/text.rb | 11 |
3 files changed, 42 insertions, 4 deletions
diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index 422b726980..753e9987d5 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -456,11 +456,27 @@ module Bundler def cooldown_excluded?(spec) return false unless spec.respond_to?(:created_at) && spec.created_at return false unless spec.respond_to?(:remote) && spec.remote + return false if pinned_by_lockfile_floor?(spec) days = spec.remote.effective_cooldown return false if days.nil? || days <= 0 (cooldown_now - spec.created_at) < (days * 86_400) end + # A spec sitting exactly at a `>= locked_version` prevent-downgrade floor is + # the version the lockfile currently pins. `bundle update` and `bundle + # outdated` install that floor so resolution never moves a gem backwards. + # Filtering it out for cooldown would then make resolution impossible + # whenever the locked version is itself inside the cooldown window, which is + # exactly what happens to a lockfile written before cooldown was enabled. + # Keep it eligible; gems being explicitly updated carry an exact `=` + # requirement instead and stay subject to the cooldown filter. + def pinned_by_lockfile_floor?(spec) + return false unless defined?(@base) && @base + requirement = base_requirements[spec.name] + return false unless requirement && !requirement.exact? + requirement.requirements.any? {|op, version| op == ">=" && version == spec.version } + end + def cooldown_now @cooldown_now ||= Time.now end diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 15d6aac0fd..a6e1dc4730 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -299,7 +299,7 @@ class Gem::Installer File.chmod(dir_mode, gem_dir) if dir_mode - say spec.post_install_message if options[:post_install_message] && !spec.post_install_message.nil? + say clean_text(spec.post_install_message.to_s) if options[:post_install_message] && !spec.post_install_message.nil? Gem::Specification.add_spec(spec) unless @install_dir @@ -712,6 +712,18 @@ class Gem::Installer if spec.dependencies.any? {|dep| dep.name =~ /(?:\R|[<>])/ } raise Gem::InstallError, "#{spec} has an invalid dependencies" end + + if spec.executables.any? {|name| !name.is_a?(String) || name != File.basename(name) || /\A\.\.?\z|\R/.match?(name) } + raise Gem::InstallError, "#{spec} has an invalid executable" + end + + raise Gem::InstallError, "#{spec} has an invalid bindir" unless spec.bindir.is_a?(String) + + expanded_gem_dir = File.expand_path(gem_dir) + expanded_bindir = File.expand_path(File.join(gem_dir, spec.bindir)) + unless expanded_bindir == expanded_gem_dir || expanded_bindir.start_with?("#{expanded_gem_dir}/") + raise Gem::InstallError, "#{spec} has an invalid bindir" + end end ## @@ -720,6 +732,7 @@ class Gem::Installer def app_script_text(bin_file_name) # NOTE: that the `load` lines cannot be indented, as old RG versions match # against the beginning of the line + escaped_bin_file_name = bin_file_name.gsub(/[\\']/) {|c| "\\#{c}" } <<~TEXT #{shebang bin_file_name} # @@ -743,9 +756,9 @@ class Gem::Installer end if Gem.respond_to?(:activate_and_load_bin_path) - Gem.activate_and_load_bin_path('#{spec.name}', '#{bin_file_name}', version) + Gem.activate_and_load_bin_path('#{spec.name}', '#{escaped_bin_file_name}', version) else - load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', version) + load Gem.activate_bin_path('#{spec.name}', '#{escaped_bin_file_name}', version) end TEXT end diff --git a/lib/rubygems/text.rb b/lib/rubygems/text.rb index 88d4ce59b4..0550dc473d 100644 --- a/lib/rubygems/text.rb +++ b/lib/rubygems/text.rb @@ -8,7 +8,16 @@ module Gem::Text # Remove any non-printable characters and make the text suitable for # printing. def clean_text(text) - text.gsub(/[\000-\b\v-\f\016-\037\177]/, ".") + text = text.gsub(/[\000-\b\v-\f\016-\037\177]/, ".") + + # Match C1 control characters (U+0080-U+009F) as codepoints. This requires + # a valid UTF-8 string so the regexp does not split a multibyte sequence; + # strings in other encodings are left unchanged. + if text.encoding == Encoding::UTF_8 && text.valid_encoding? + text = text.gsub(/[\u0080-\u009f]/, ".") + end + + text end def truncate_text(text, description, max_length = 100_000) |
