@@ -89,6 +89,18 @@ class TooLongFileName < Error; end
8989
9090 class TarInvalidError < Error ; end
9191
92+ ##
93+ # Raised when a filename contains characters that are invalid on Windows
94+
95+ class InvalidFileNameError < Error
96+ def initialize ( filename , gem_name = nil )
97+ message = "The gem contains a file '#{ filename } ' with characters in its name that are not allowed on Windows (e.g., colons)."
98+ message += " This is a problem with the '#{ gem_name } ' gem, not Rubygems." if gem_name
99+ message += " Please report this issue to the gem author."
100+ super message
101+ end
102+ end
103+
92104 attr_accessor :build_time # :nodoc:
93105
94106 ##
@@ -258,6 +270,10 @@ def add_contents(tar) # :nodoc:
258270
259271 def add_files ( tar ) # :nodoc:
260272 @spec . files . each do |file |
273+ if invalid_windows_filename? ( file )
274+ alert_warning "filename '#{ file } ' contains characters that are invalid on Windows (e.g., colons). This gem may fail to install on Windows."
275+ end
276+
261277 stat = File . lstat file
262278
263279 if stat . symlink?
@@ -427,6 +443,11 @@ def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc:
427443
428444 destination = install_location full_name , destination_dir
429445
446+ if invalid_windows_filename? ( full_name )
447+ gem_name = @spec ? @spec . full_name : "unknown"
448+ raise Gem ::Package ::InvalidFileNameError . new ( full_name , gem_name )
449+ end
450+
430451 if entry . symlink?
431452 link_target = entry . header . linkname
432453 real_destination = link_target . start_with? ( "/" ) ? link_target : File . expand_path ( link_target , File . dirname ( destination ) )
@@ -450,13 +471,18 @@ def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc:
450471 end
451472
452473 if entry . file?
453- File . open ( destination , "wb" ) do |out |
454- copy_stream ( tar . io , out , entry . size )
455- # Flush needs to happen before chmod because there could be data
456- # in the IO buffer that needs to be written, and that could be
457- # written after the chmod (on close) which would mess up the perms
458- out . flush
459- out . chmod file_mode ( entry . header . mode ) & ~File . umask
474+ begin
475+ File . open ( destination , "wb" ) do |out |
476+ copy_stream ( tar . io , out , entry . size )
477+ # Flush needs to happen before chmod because there could be data
478+ # in the IO buffer that needs to be written, and that could be
479+ # written after the chmod (on close) which would mess up the perms
480+ out . flush
481+ out . chmod file_mode ( entry . header . mode ) & ~File . umask
482+ end
483+ rescue Errno ::EINVAL => e
484+ gem_name = @spec ? @spec . full_name : "unknown"
485+ raise Gem ::Package ::InvalidFileNameError . new ( full_name , gem_name ) , e . message
460486 end
461487 end
462488
@@ -529,6 +555,19 @@ def normalize_path(pathname) # :nodoc:
529555 end
530556 end
531557
558+ ##
559+ # Checks if a filename contains characters that are invalid on Windows.
560+ # Windows doesn't allow: < > : " | ? * \ and control characters (0x00-0x1F).
561+ # Colons are the most common issue since they're allowed on Unix.
562+ # Note: Colons are only valid as drive letter separators (e.g., C:), not in filenames.
563+
564+ def invalid_windows_filename? ( filename ) # :nodoc:
565+ return false unless Gem . win_platform?
566+
567+ basename = File . basename ( filename )
568+ basename . match? ( /[:<>"|?*\\ \x00 -\x1f ]/ )
569+ end
570+
532571 ##
533572 # Loads a Gem::Specification from the TarEntry +entry+
534573
0 commit comments