Skip to content
Open
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
31 changes: 31 additions & 0 deletions lib/rubygems/package.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,18 @@ class TooLongFileName < Error; end

class TarInvalidError < Error; end

##
# Raised when a filename contains characters that are invalid on Windows

class InvalidWindowsFileNameError < Error
def initialize(filename, gem_name = nil)
message = "The gem contains a file '#{filename}' with characters in its name that are not allowed on Windows (e.g., colons)."
message += " This is a problem with the '#{gem_name}' gem, not Rubygems." if gem_name
message += " Please report this issue to the gem author."
super message
end
end

attr_accessor :build_time # :nodoc:

##
Expand Down Expand Up @@ -258,6 +270,10 @@ def add_contents(tar) # :nodoc:

def add_files(tar) # :nodoc:
@spec.files.each do |file|
if invalid_windows_filename?(file)
alert_warning "filename '#{file}' contains characters that are invalid on Windows (e.g., colons). This gem may fail to install on Windows."
end

stat = File.lstat file

if stat.symlink?
Expand Down Expand Up @@ -425,6 +441,11 @@ def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc:
full_name = entry.full_name
next unless File.fnmatch pattern, full_name, File::FNM_DOTMATCH

if Gem.win_platform? && invalid_windows_filename?(full_name)
gem_name = @spec ? @spec.full_name : "unknown"
raise Gem::Package::InvalidWindowsFileNameError.new(full_name, gem_name)
end

destination = install_location full_name, destination_dir

if entry.symlink?
Expand Down Expand Up @@ -529,6 +550,16 @@ def normalize_path(pathname) # :nodoc:
end
end

##
# Checks if a filename contains characters that are invalid on Windows.
# Windows doesn't allow: < > : " | ? * \ and control characters (0x00-0x1F).
# Colons are the most common issue since they're allowed on Unix.
# Note: Colons are only valid as drive letter separators (e.g., C:), not in filenames.

def invalid_windows_filename?(filename) # :nodoc:
filename.to_s.split("/").any? { |part| part.match?(/[:<>"|?*\\\x00-\x1f]/) }
end

##
# Loads a Gem::Specification from the TarEntry +entry+

Expand Down
71 changes: 71 additions & 0 deletions test/rubygems/test_gem_package.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1303,4 +1303,75 @@ def test_contents_from_io

assert_equal %w[lib/code.rb], package.contents
end

def test_invalid_windows_filename
package = Gem::Package.new @gem

if Gem.win_platform?
assert package.invalid_windows_filename?("spec/internal/:memory")
assert package.invalid_windows_filename?("file:name.rb")
assert package.invalid_windows_filename?("file<name.rb")
assert package.invalid_windows_filename?('file"name.rb')
end
end

def test_invalid_file_name_error_message
error = Gem::Package::InvalidWindowsFileNameError.new("spec/internal/:memory", "crono-2.0.1")
assert_match(%r{The gem contains a file 'spec/internal/:memory'}, error.message)
assert_match(/characters in its name that are not allowed on Windows/, error.message)
assert_match(/This is a problem with the 'crono-2.0.1' gem, not Rubygems/, error.message)
assert_match(/Please report this issue to the gem author/, error.message)
end

def test_extract_tar_gz_invalid_filename
pend "Windows filename validation only applies on Windows" unless Gem.win_platform?

package = Gem::Package.new @gem
package.verify

tgz_io = util_tar_gz do |tar|
tar.add_file "spec/internal/:memory", 0o644 do |io|
io.write "test content"
end
end

e = assert_raise Gem::Package::InvalidWindowsFileNameError do
package.extract_tar_gz tgz_io, @destination
end

assert_match(%r{The gem contains a file 'spec/internal/:memory'}, e.message)
assert_match(/characters in its name that are not allowed on Windows/, e.message)
assert_match(/This is a problem with the 'a-2' gem, not Rubygems/, e.message)
end

def test_build_warns_on_invalid_windows_filename
pend "Windows filename validation only applies on non-Windows" if Gem.win_platform?

spec = Gem::Specification.new "test_gem", "1.0"
spec.summary = "test"
spec.authors = "test"
spec.files = ["lib/code.rb", "lib/file:name.rb"]

FileUtils.mkdir "lib"

File.open "lib/code.rb", "w" do |io|
io.write "# lib/code.rb"
end

File.open "lib/file:name.rb", "w" do |io|
io.write "# lib/file:name.rb"
end

package = Gem::Package.new spec.file_name
package.spec = spec

ui = Gem::MockGemUi.new
use_ui ui do
package.build
end

assert_match(%r{filename 'lib/file:name\.rb' contains characters that are invalid on Windows}, ui.error)
assert_match(/This gem may fail to install on Windows/, ui.error)
assert_path_exist spec.file_name
end
end