1# coding: UTF-8 2 3require 'rubygems/package/tar_test_case' 4require 'rubygems/simple_gem' 5 6class TestGemPackage < Gem::Package::TarTestCase 7 8 def setup 9 super 10 11 @spec = quick_gem 'a' do |s| 12 s.description = 'π' 13 s.files = %w[lib/code.rb] 14 end 15 16 util_build_gem @spec 17 18 @gem = @spec.cache_file 19 20 @destination = File.join @tempdir, 'extract' 21 22 FileUtils.mkdir_p @destination 23 end 24 25 def test_class_new_old_format 26 open 'old_format.gem', 'wb' do |io| 27 io.write SIMPLE_GEM 28 end 29 30 package = Gem::Package.new 'old_format.gem' 31 32 assert package.spec 33 end 34 35 def test_add_checksums 36 gem_io = StringIO.new 37 38 spec = Gem::Specification.new 'build', '1' 39 spec.summary = 'build' 40 spec.authors = 'build' 41 spec.files = ['lib/code.rb'] 42 spec.date = Time.at 0 43 spec.rubygems_version = Gem::Version.new '0' 44 45 FileUtils.mkdir 'lib' 46 47 open 'lib/code.rb', 'w' do |io| 48 io.write '# lib/code.rb' 49 end 50 51 package = Gem::Package.new spec.file_name 52 package.spec = spec 53 package.build_time = 1 # 0 uses current time 54 package.setup_signer 55 56 Gem::Package::TarWriter.new gem_io do |gem| 57 package.add_metadata gem 58 package.add_contents gem 59 package.add_checksums gem 60 end 61 62 gem_io.rewind 63 64 reader = Gem::Package::TarReader.new gem_io 65 66 checksums = nil 67 tar = nil 68 69 reader.each_entry do |entry| 70 case entry.full_name 71 when 'checksums.yaml.gz' then 72 Zlib::GzipReader.wrap entry do |io| 73 checksums = io.read 74 end 75 when 'data.tar.gz' then 76 tar = entry.read 77 end 78 end 79 80 s = StringIO.new 81 82 package.gzip_to s do |io| 83 io.write spec.to_yaml 84 end 85 86 metadata_sha1 = Digest::SHA1.hexdigest s.string 87 metadata_sha512 = Digest::SHA512.hexdigest s.string 88 89 expected = { 90 'SHA1' => { 91 'metadata.gz' => metadata_sha1, 92 'data.tar.gz' => Digest::SHA1.hexdigest(tar), 93 }, 94 'SHA512' => { 95 'metadata.gz' => metadata_sha512, 96 'data.tar.gz' => Digest::SHA512.hexdigest(tar), 97 } 98 } 99 100 assert_equal expected, YAML.load(checksums) 101 end 102 103 def test_add_files 104 spec = Gem::Specification.new 105 spec.files = %w[lib/code.rb lib/empty] 106 107 FileUtils.mkdir_p 'lib/empty' 108 109 open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end 110 open 'lib/extra.rb', 'w' do |io| io.write '# lib/extra.rb' end 111 112 package = Gem::Package.new 'bogus.gem' 113 package.spec = spec 114 115 tar = util_tar do |tar_io| 116 package.add_files tar_io 117 end 118 119 tar.rewind 120 121 files = [] 122 123 Gem::Package::TarReader.new tar do |tar_io| 124 tar_io.each_entry do |entry| 125 files << entry.full_name 126 end 127 end 128 129 assert_equal %w[lib/code.rb], files 130 end 131 132 def test_build 133 spec = Gem::Specification.new 'build', '1' 134 spec.summary = 'build' 135 spec.authors = 'build' 136 spec.files = ['lib/code.rb'] 137 spec.rubygems_version = :junk 138 139 FileUtils.mkdir 'lib' 140 141 open 'lib/code.rb', 'w' do |io| 142 io.write '# lib/code.rb' 143 end 144 145 package = Gem::Package.new spec.file_name 146 package.spec = spec 147 148 package.build 149 150 assert_equal Gem::VERSION, spec.rubygems_version 151 assert_path_exists spec.file_name 152 153 reader = Gem::Package.new spec.file_name 154 assert_equal spec, reader.spec 155 156 assert_equal %w[metadata.gz data.tar.gz checksums.yaml.gz], 157 reader.files 158 159 assert_equal %w[lib/code.rb], reader.contents 160 end 161 162 def test_build_auto_signed 163 FileUtils.mkdir_p File.join(Gem.user_home, '.gem') 164 165 private_key_path = File.join Gem.user_home, '.gem', 'gem-private_key.pem' 166 Gem::Security.write PRIVATE_KEY, private_key_path 167 168 public_cert_path = File.join Gem.user_home, '.gem', 'gem-public_cert.pem' 169 Gem::Security.write PUBLIC_CERT, public_cert_path 170 171 spec = Gem::Specification.new 'build', '1' 172 spec.summary = 'build' 173 spec.authors = 'build' 174 spec.files = ['lib/code.rb'] 175 176 FileUtils.mkdir 'lib' 177 178 open 'lib/code.rb', 'w' do |io| 179 io.write '# lib/code.rb' 180 end 181 182 package = Gem::Package.new spec.file_name 183 package.spec = spec 184 185 package.build 186 187 assert_equal Gem::VERSION, spec.rubygems_version 188 assert_path_exists spec.file_name 189 190 reader = Gem::Package.new spec.file_name 191 assert reader.verify 192 193 assert_equal [PUBLIC_CERT.to_pem], reader.spec.cert_chain 194 195 assert_equal %w[metadata.gz metadata.gz.sig 196 data.tar.gz data.tar.gz.sig 197 checksums.yaml.gz checksums.yaml.gz.sig], 198 reader.files 199 200 assert_equal %w[lib/code.rb], reader.contents 201 end 202 203 def test_build_invalid 204 spec = Gem::Specification.new 'build', '1' 205 206 package = Gem::Package.new spec.file_name 207 package.spec = spec 208 209 e = assert_raises Gem::InvalidSpecificationException do 210 package.build 211 end 212 213 assert_equal 'missing value for attribute summary', e.message 214 end 215 216 def test_build_signed 217 spec = Gem::Specification.new 'build', '1' 218 spec.summary = 'build' 219 spec.authors = 'build' 220 spec.files = ['lib/code.rb'] 221 spec.cert_chain = [PUBLIC_CERT.to_pem] 222 spec.signing_key = PRIVATE_KEY 223 224 FileUtils.mkdir 'lib' 225 226 open 'lib/code.rb', 'w' do |io| 227 io.write '# lib/code.rb' 228 end 229 230 package = Gem::Package.new spec.file_name 231 package.spec = spec 232 233 package.build 234 235 assert_equal Gem::VERSION, spec.rubygems_version 236 assert_path_exists spec.file_name 237 238 reader = Gem::Package.new spec.file_name 239 assert reader.verify 240 241 assert_equal spec, reader.spec 242 243 assert_equal %w[metadata.gz metadata.gz.sig 244 data.tar.gz data.tar.gz.sig 245 checksums.yaml.gz checksums.yaml.gz.sig], 246 reader.files 247 248 assert_equal %w[lib/code.rb], reader.contents 249 end 250 251 def test_contents 252 package = Gem::Package.new @gem 253 254 assert_equal %w[lib/code.rb], package.contents 255 end 256 257 def test_extract_files 258 package = Gem::Package.new @gem 259 260 package.extract_files @destination 261 262 extracted = File.join @destination, 'lib/code.rb' 263 assert_path_exists extracted 264 265 mask = 0100666 & (~File.umask) 266 267 assert_equal mask.to_s(8), File.stat(extracted).mode.to_s(8) unless 268 win_platform? 269 end 270 271 def test_extract_files_empty 272 data_tgz = util_tar_gz do end 273 274 gem = util_tar do |tar| 275 tar.add_file 'data.tar.gz', 0644 do |io| 276 io.write data_tgz.string 277 end 278 279 tar.add_file 'metadata.gz', 0644 do |io| 280 Zlib::GzipWriter.wrap io do |gzio| 281 gzio.write @spec.to_yaml 282 end 283 end 284 end 285 286 open 'empty.gem', 'wb' do |io| 287 io.write gem.string 288 end 289 290 package = Gem::Package.new 'empty.gem' 291 292 package.extract_files @destination 293 294 assert_path_exists @destination 295 end 296 297 def test_extract_tar_gz_absolute 298 package = Gem::Package.new @gem 299 300 tgz_io = util_tar_gz do |tar| 301 tar.add_file '/absolute.rb', 0644 do |io| io.write 'hi' end 302 end 303 304 e = assert_raises Gem::Package::PathError do 305 package.extract_tar_gz tgz_io, @destination 306 end 307 308 assert_equal("installing into parent path /absolute.rb of " + 309 "#{@destination} is not allowed", e.message) 310 end 311 312 def test_install_location 313 package = Gem::Package.new @gem 314 315 file = 'file.rb' 316 file.taint 317 318 destination = package.install_location file, @destination 319 320 assert_equal File.join(@destination, 'file.rb'), destination 321 refute destination.tainted? 322 end 323 324 def test_install_location_absolute 325 package = Gem::Package.new @gem 326 327 e = assert_raises Gem::Package::PathError do 328 package.install_location '/absolute.rb', @destination 329 end 330 331 assert_equal("installing into parent path /absolute.rb of " + 332 "#{@destination} is not allowed", e.message) 333 end 334 335 def test_install_location_extra_slash 336 skip 'no File.realpath on 1.8' if RUBY_VERSION < '1.9' 337 package = Gem::Package.new @gem 338 339 file = 'foo//file.rb' 340 file.taint 341 342 destination = @destination.sub '/', '//' 343 344 destination = package.install_location file, destination 345 346 assert_equal File.join(@destination, 'foo', 'file.rb'), destination 347 refute destination.tainted? 348 end 349 350 def test_install_location_relative 351 package = Gem::Package.new @gem 352 353 e = assert_raises Gem::Package::PathError do 354 package.install_location '../relative.rb', @destination 355 end 356 357 parent = File.expand_path File.join @destination, "../relative.rb" 358 359 assert_equal("installing into parent path #{parent} of " + 360 "#{@destination} is not allowed", e.message) 361 end 362 363 def test_load_spec 364 entry = StringIO.new Gem.gzip @spec.to_yaml 365 def entry.full_name() 'metadata.gz' end 366 367 package = Gem::Package.new 'nonexistent.gem' 368 369 spec = package.load_spec entry 370 371 assert_equal @spec, spec 372 end 373 374 def test_verify 375 package = Gem::Package.new @gem 376 377 package.verify 378 379 assert_equal @spec, package.spec 380 assert_equal %w[checksums.yaml.gz data.tar.gz metadata.gz], 381 package.files.sort 382 end 383 384 def test_verify_checksum_bad 385 data_tgz = util_tar_gz do |tar| 386 tar.add_file 'lib/code.rb', 0444 do |io| 387 io.write '# lib/code.rb' 388 end 389 end 390 391 data_tgz = data_tgz.string 392 393 gem = util_tar do |tar| 394 metadata_gz = Gem.gzip @spec.to_yaml 395 396 tar.add_file 'metadata.gz', 0444 do |io| 397 io.write metadata_gz 398 end 399 400 tar.add_file 'data.tar.gz', 0444 do |io| 401 io.write data_tgz 402 end 403 404 bogus_checksums = { 405 'SHA1' => { 406 'data.tar.gz' => 'bogus', 407 'metadata.gz' => 'bogus', 408 }, 409 } 410 tar.add_file 'checksums.yaml.gz', 0444 do |io| 411 Zlib::GzipWriter.wrap io do |gz_io| 412 gz_io.write YAML.dump bogus_checksums 413 end 414 end 415 end 416 417 open 'mismatch.gem', 'wb' do |io| 418 io.write gem.string 419 end 420 421 package = Gem::Package.new 'mismatch.gem' 422 423 e = assert_raises Gem::Package::FormatError do 424 package.verify 425 end 426 427 assert_equal 'SHA1 checksum mismatch for data.tar.gz in mismatch.gem', 428 e.message 429 end 430 431 def test_verify_checksum_missing 432 data_tgz = util_tar_gz do |tar| 433 tar.add_file 'lib/code.rb', 0444 do |io| 434 io.write '# lib/code.rb' 435 end 436 end 437 438 data_tgz = data_tgz.string 439 440 gem = util_tar do |tar| 441 metadata_gz = Gem.gzip @spec.to_yaml 442 443 tar.add_file 'metadata.gz', 0444 do |io| 444 io.write metadata_gz 445 end 446 447 digest = OpenSSL::Digest::SHA1.new 448 digest << metadata_gz 449 450 checksums = { 451 'SHA1' => { 452 'metadata.gz' => digest.hexdigest, 453 }, 454 } 455 456 tar.add_file 'checksums.yaml.gz', 0444 do |io| 457 Zlib::GzipWriter.wrap io do |gz_io| 458 gz_io.write YAML.dump checksums 459 end 460 end 461 462 tar.add_file 'data.tar.gz', 0444 do |io| 463 io.write data_tgz 464 end 465 end 466 467 open 'data_checksum_missing.gem', 'wb' do |io| 468 io.write gem.string 469 end 470 471 package = Gem::Package.new 'data_checksum_missing.gem' 472 473 assert package.verify 474 end 475 476 def test_verify_corrupt 477 Tempfile.open 'corrupt' do |io| 478 data = Gem.gzip 'a' * 10 479 io.write tar_file_header('metadata.gz', "\000x", 0644, data.length) 480 io.write data 481 io.rewind 482 483 package = Gem::Package.new io.path 484 485 e = assert_raises Gem::Package::FormatError do 486 package.verify 487 end 488 489 assert_equal "tar is corrupt, name contains null byte in #{io.path}", 490 e.message 491 end 492 end 493 494 def test_verify_empty 495 FileUtils.touch 'empty.gem' 496 497 package = Gem::Package.new 'empty.gem' 498 499 e = assert_raises Gem::Package::FormatError do 500 package.verify 501 end 502 503 assert_equal 'package metadata is missing in empty.gem', e.message 504 end 505 506 def test_verify_nonexistent 507 package = Gem::Package.new 'nonexistent.gem' 508 509 e = assert_raises Gem::Package::FormatError do 510 package.verify 511 end 512 513 assert_match %r%^No such file or directory%, e.message 514 assert_match %r%nonexistent.gem$%, e.message 515 end 516 517 def test_verify_security_policy 518 package = Gem::Package.new @gem 519 package.security_policy = Gem::Security::HighSecurity 520 521 e = assert_raises Gem::Security::Exception do 522 package.verify 523 end 524 525 assert_equal 'unsigned gems are not allowed by the High Security policy', 526 e.message 527 528 refute package.instance_variable_get(:@spec), '@spec must not be loaded' 529 assert_empty package.instance_variable_get(:@files), '@files must empty' 530 end 531 532 def test_verify_security_policy_low_security 533 @spec.cert_chain = [PUBLIC_CERT.to_pem] 534 @spec.signing_key = PRIVATE_KEY 535 536 FileUtils.mkdir_p 'lib' 537 FileUtils.touch 'lib/code.rb' 538 539 build = Gem::Package.new @gem 540 build.spec = @spec 541 542 build.build 543 544 package = Gem::Package.new @gem 545 package.security_policy = Gem::Security::LowSecurity 546 547 assert package.verify 548 end 549 550 def test_verify_security_policy_checksum_missing 551 @spec.cert_chain = [PUBLIC_CERT.to_pem] 552 @spec.signing_key = PRIVATE_KEY 553 554 build = Gem::Package.new @gem 555 build.spec = @spec 556 build.setup_signer 557 558 FileUtils.mkdir 'lib' 559 FileUtils.touch 'lib/code.rb' 560 561 open @gem, 'wb' do |gem_io| 562 Gem::Package::TarWriter.new gem_io do |gem| 563 build.add_metadata gem 564 build.add_contents gem 565 566 # write bogus data.tar.gz to foil signature 567 bogus_data = Gem.gzip 'hello' 568 gem.add_file_simple 'data.tar.gz', 0444, bogus_data.length do |io| 569 io.write bogus_data 570 end 571 572 # pre rubygems 2.0 gems do not add checksums 573 end 574 end 575 576 Gem::Security.trust_dir.trust_cert PUBLIC_CERT 577 578 package = Gem::Package.new @gem 579 package.security_policy = Gem::Security::HighSecurity 580 581 e = assert_raises Gem::Security::Exception do 582 package.verify 583 end 584 585 assert_equal 'invalid signature', e.message 586 587 refute package.instance_variable_get(:@spec), '@spec must not be loaded' 588 assert_empty package.instance_variable_get(:@files), '@files must empty' 589 end 590 591 def test_verify_truncate 592 open 'bad.gem', 'wb' do |io| 593 io.write File.read(@gem, 1024) # don't care about newlines 594 end 595 596 package = Gem::Package.new 'bad.gem' 597 598 e = assert_raises Gem::Package::FormatError do 599 package.verify 600 end 601 602 assert_equal 'package content (data.tar.gz) is missing in bad.gem', 603 e.message 604 end 605 606 def test_spec 607 package = Gem::Package.new @gem 608 609 assert_equal @spec, package.spec 610 end 611 612 def util_tar 613 tar_io = StringIO.new 614 615 Gem::Package::TarWriter.new tar_io do |tar| 616 yield tar 617 end 618 619 tar_io.rewind 620 621 tar_io 622 end 623 624 def util_tar_gz(&block) 625 tar_io = util_tar(&block) 626 627 tgz_io = StringIO.new 628 629 # can't wrap TarWriter because it seeks 630 Zlib::GzipWriter.wrap tgz_io do |io| io.write tar_io.string end 631 632 StringIO.new tgz_io.string 633 end 634 635end 636 637