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
2 changes: 1 addition & 1 deletion lib/rubygems/installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ def install

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

Expand Down
11 changes: 10 additions & 1 deletion lib/rubygems/text.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
33 changes: 33 additions & 0 deletions test/rubygems/test_gem_installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1481,6 +1481,39 @@ def test_install_with_skipped_message
refute_match(/I am a shiny gem!/, @ui.output)
end

def test_install_sanitizes_post_install_message
# Use for_spec so the in-memory message reaches the installer verbatim;
# building a gem would escape the control characters during serialization.
@spec = setup_base_spec
@spec.post_install_message = "shiny \e]2;pwn\a gem"

installer = Gem::Installer.for_spec @spec, post_install_message: true
installer.gem_home = @gemhome

use_ui @ui do
installer.install
end

assert_match(/shiny \.\]2;pwn\. gem/, @ui.output)
refute_match(/\e\]2;pwn/, @ui.output)
end

def test_install_handles_non_string_post_install_message
# post_install_message may be a non-String (the gemspec schema allows an
# array), so sanitizing must not assume it responds to gsub.
@spec = setup_base_spec
@spec.post_install_message = %w[one two]

installer = Gem::Installer.for_spec @spec, post_install_message: true
installer.gem_home = @gemhome

use_ui @ui do
installer.install
end

assert_match(/one/, @ui.output)
end

def test_install_extension_dir
gemhome2 = "#{@gemhome}2"

Expand Down
17 changes: 17 additions & 0 deletions test/rubygems/test_gem_text.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,21 @@ def test_truncate_text
def test_clean_text
assert_equal ".]2;nyan.", clean_text("\e]2;nyan\a")
end

def test_clean_text_strips_c1_control_characters
text = [0x41, 0x9b, 0x42].pack("U*") # "A", CSI (U+009B), "B"
assert_equal "A.B", clean_text(text)
end

def test_clean_text_preserves_multibyte_characters
# U+0400 encodes to bytes D0 80, whose 0x80 continuation byte must not be
# mistaken for a C1 control byte. NEL (U+0085) is stripped.
text = [0x400, 0x85].pack("U*")
assert_equal [0x400, 0x2e].pack("U*"), clean_text(text)
end
Comment on lines +109 to +114

def test_clean_text_passes_through_non_unicode_encodings
text = "x\x9by".dup.force_encoding("ISO-8859-1")
assert_equal text, clean_text(text)
end
end
Loading