Project

General

Profile

Bug #14481 ยป rubygems-276-for-ruby24.patch

hsbt (Hiroshi SHIBATA), 02/16/2018 10:55 AM

View differences:

lib/rubygems.rb
10 10
require 'thread'
11 11

  
12 12
module Gem
13
  VERSION = "2.6.14"
13
  VERSION = "2.6.14.1"
14 14
end
15 15

  
16 16
# Must be first since it unloads the prelude from 1.9.2
lib/rubygems/commands/owner_command.rb
62 62
    end
63 63

  
64 64
    with_response response do |resp|
65
      owners = YAML.load resp.body
65
      owners = Gem::SafeYAML.load resp.body
66 66

  
67 67
      say "Owners for gem: #{name}"
68 68
      owners.each do |owner|
lib/rubygems/package.rb
378 378
            File.dirname destination
379 379
          end
380 380

  
381
        FileUtils.mkdir_p mkdir, mkdir_options
381
        mkdir_p_safe mkdir, mkdir_options, destination_dir, entry.full_name
382 382

  
383 383
        open destination, 'wb' do |out|
384 384
          out.write entry.read
......
416 416
    raise Gem::Package::PathError.new(filename, destination_dir) if
417 417
      filename.start_with? '/'
418 418

  
419
    destination_dir = File.realpath destination_dir if
420
      File.respond_to? :realpath
419
    destination_dir = realpath destination_dir
421 420
    destination_dir = File.expand_path destination_dir
422 421

  
423 422
    destination = File.join destination_dir, filename
424 423
    destination = File.expand_path destination
425 424

  
426 425
    raise Gem::Package::PathError.new(destination, destination_dir) unless
427
      destination.start_with? destination_dir
426
      destination.start_with? destination_dir + '/'
428 427

  
429 428
    destination.untaint
430 429
    destination
431 430
  end
432 431

  
432
  def mkdir_p_safe mkdir, mkdir_options, destination_dir, file_name
433
    destination_dir = realpath File.expand_path(destination_dir)
434
    parts = mkdir.split(File::SEPARATOR)
435
    parts.reduce do |path, basename|
436
      path = realpath path  unless path == ""
437
      path = File.expand_path(path + File::SEPARATOR + basename)
438
      lstat = File.lstat path rescue nil
439
      if !lstat || !lstat.directory?
440
        unless path.start_with? destination_dir and (FileUtils.mkdir path, mkdir_options rescue false)
441
          raise Gem::Package::PathError.new(file_name, destination_dir)
442
        end
443
      end
444
      path
445
    end
446
  end
447

  
433 448
  ##
434 449
  # Loads a Gem::Specification from the TarEntry +entry+
435 450

  
......
603 618
      raise Gem::Package::FormatError.new \
604 619
              'package content (data.tar.gz) is missing', @gem
605 620
    end
621

  
622
    if duplicates = @files.group_by {|f| f }.select {|k,v| v.size > 1 }.map(&:first) and duplicates.any?
623
      raise Gem::Security::Exception, "duplicate files in the package: (#{duplicates.map(&:inspect).join(', ')})"
624
    end
606 625
  end
607 626

  
608 627
  ##
......
616 635
    raise Gem::Package::FormatError.new(e.message, entry.full_name)
617 636
  end
618 637

  
638
  if File.respond_to? :realpath
639
    def realpath file
640
      File.realpath file
641
    end
642
  else
643
    def realpath file
644
      file
645
    end
646
  end
647

  
619 648
end
620 649

  
621 650
require 'rubygems/package/digest_io'
lib/rubygems/package/tar_header.rb
104 104
    fields = header.unpack UNPACK_FORMAT
105 105

  
106 106
    new :name     => fields.shift,
107
        :mode     => fields.shift.oct,
108
        :uid      => fields.shift.oct,
109
        :gid      => fields.shift.oct,
110
        :size     => fields.shift.oct,
111
        :mtime    => fields.shift.oct,
112
        :checksum => fields.shift.oct,
107
        :mode     => strict_oct(fields.shift),
108
        :uid      => strict_oct(fields.shift),
109
        :gid      => strict_oct(fields.shift),
110
        :size     => strict_oct(fields.shift),
111
        :mtime    => strict_oct(fields.shift),
112
        :checksum => strict_oct(fields.shift),
113 113
        :typeflag => fields.shift,
114 114
        :linkname => fields.shift,
115 115
        :magic    => fields.shift,
116
        :version  => fields.shift.oct,
116
        :version  => strict_oct(fields.shift),
117 117
        :uname    => fields.shift,
118 118
        :gname    => fields.shift,
119
        :devmajor => fields.shift.oct,
120
        :devminor => fields.shift.oct,
119
        :devmajor => strict_oct(fields.shift),
120
        :devminor => strict_oct(fields.shift),
121 121
        :prefix   => fields.shift,
122 122

  
123 123
        :empty => empty
124 124
  end
125 125

  
126
  def self.strict_oct(str)
127
    return str.oct if str =~ /\A[0-7]*\z/
128
    raise ArgumentError, "#{str.inspect} is not an octal string"
129
  end
130

  
126 131
  ##
127 132
  # Creates a new TarHeader using +vals+
128 133

  
lib/rubygems/package/tar_writer.rb
196 196
      digest_name == signer.digest_name
197 197
    end
198 198

  
199
    raise "no #{signer.digest_name} in #{digests.values.compact}" unless signature_digest
200

  
199 201
    if signer.key then
200 202
      signature = signer.sign signature_digest.digest
201 203

  
lib/rubygems/server.rb
631 631
      executables = nil if executables.empty?
632 632
      executables.last["is_last"] = true if executables
633 633

  
634
      # Pre-process spec homepage for safety reasons
635
      begin
636
        homepage_uri = URI.parse(spec.homepage)
637
        if [URI::HTTP, URI::HTTPS].member? homepage_uri.class
638
          homepage_uri = spec.homepage
639
        else
640
          homepage_uri = "."
641
        end
642
      rescue URI::InvalidURIError
643
        homepage_uri = "."
644
      end
645

  
634 646
      specs << {
635 647
        "authors"             => spec.authors.sort.join(", "),
636 648
        "date"                => spec.date.to_s,
......
640 652
        "only_one_executable" => (executables && executables.size == 1),
641 653
        "full_name"           => spec.full_name,
642 654
        "has_deps"            => !deps.empty?,
643
        "homepage"            => spec.homepage,
655
        "homepage"            => homepage_uri,
644 656
        "name"                => spec.name,
645 657
        "rdoc_installed"      => Gem::RDoc.new(spec).rdoc_installed?,
646 658
        "ri_installed"        => Gem::RDoc.new(spec).ri_installed?,
lib/rubygems/specification.rb
15 15
require 'rubygems/stub_specification'
16 16
require 'rubygems/util/list'
17 17
require 'stringio'
18
require 'uri'
18 19

  
19 20
##
20 21
# The Specification class contains the information for a Gem.  Typically
......
2813 2814
      raise Gem::InvalidSpecificationException, "#{lazy} is not a summary"
2814 2815
    end
2815 2816

  
2816
    if homepage and not homepage.empty? and
2817
       homepage !~ /\A[a-z][a-z\d+.-]*:/i then
2818
      raise Gem::InvalidSpecificationException,
2819
            "\"#{homepage}\" is not a URI"
2817
    # Make sure a homepage is valid HTTP/HTTPS URI
2818
    if homepage and not homepage.empty?
2819
      begin
2820
        homepage_uri = URI.parse(homepage)
2821
        unless [URI::HTTP, URI::HTTPS].member? homepage_uri.class
2822
          raise Gem::InvalidSpecificationException, "\"#{homepage}\" is not a valid HTTP URI"
2823
        end
2824
      rescue URI::InvalidURIError
2825
        raise Gem::InvalidSpecificationException, "\"#{homepage}\" is not a valid HTTP URI"
2826
      end
2820 2827
    end
2821 2828

  
2822 2829
    # Warnings
test/rubygems/test_gem_commands_owner_command.rb
43 43
    assert_match %r{- 4}, @ui.output
44 44
  end
45 45

  
46
  def test_show_owners_dont_load_objects
47
    skip "testing a psych-only API" unless defined?(::Psych::DisallowedClass)
48

  
49
    response = <<EOF
50
---
51
- email: !ruby/object:Object {}
52
  id: 1
53
  handle: user1
54
- email: user2@example.com
55
- id: 3
56
  handle: user3
57
- id: 4
58
EOF
59

  
60
    @fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners.yaml"] = [response, 200, 'OK']
61

  
62
    assert_raises Psych::DisallowedClass do
63
      use_ui @ui do
64
        @cmd.show_owners("freewill")
65
      end
66
    end
67

  
68
  end
69

  
70

  
46 71
  def test_show_owners_setting_up_host_through_env_var
47 72
    response = "- email: user1@example.com\n"
48 73
    host = "http://rubygems.example"
test/rubygems/test_gem_package.rb
455 455
                 File.read(extracted)
456 456
  end
457 457

  
458
  def test_extract_symlink_parent
459
   skip 'symlink not supported' if Gem.win_platform?
460

  
461
   package = Gem::Package.new @gem
462

  
463
   tgz_io = util_tar_gz do |tar|
464
     tar.mkdir       'lib',               0755
465
     tar.add_symlink 'lib/link', '../..', 0644
466
     tar.add_file    'lib/link/outside.txt', 0644 do |io| io.write 'hi' end
467
   end
468

  
469
   # Extract into a subdirectory of @destination; if this test fails it writes
470
   # a file outside destination_subdir, but we want the file to remain inside
471
   # @destination so it will be cleaned up.
472
   destination_subdir = File.join @destination, 'subdir'
473
   FileUtils.mkdir_p destination_subdir
474

  
475
   e = assert_raises Gem::Package::PathError do
476
     package.extract_tar_gz tgz_io, destination_subdir
477
   end
478

  
479
   assert_equal("installing into parent path lib/link/outside.txt of " +
480
                 "#{destination_subdir} is not allowed", e.message)
481
  end
482

  
458 483
  def test_extract_tar_gz_directory
459 484
    package = Gem::Package.new @gem
460 485

  
......
566 591
                 "#{@destination} is not allowed", e.message)
567 592
  end
568 593

  
594
  def test_install_location_suffix
595
    package = Gem::Package.new @gem
596

  
597
    filename = "../#{File.basename(@destination)}suffix.rb"
598

  
599
    e = assert_raises Gem::Package::PathError do
600
      package.install_location filename, @destination
601
    end
602

  
603
    parent = File.expand_path File.join @destination, filename
604

  
605
    assert_equal("installing into parent path #{parent} of " +
606
                 "#{@destination} is not allowed", e.message)
607
  end
608

  
569 609
  def test_load_spec
570 610
    entry = StringIO.new Gem.gzip @spec.to_yaml
571 611
    def entry.full_name() 'metadata.gz' end
......
723 763
    assert_match %r%nonexistent.gem$%,           e.message
724 764
  end
725 765

  
766
  def test_verify_duplicate_file
767
    FileUtils.mkdir_p 'lib'
768
    FileUtils.touch 'lib/code.rb'
769

  
770
    build = Gem::Package.new @gem
771
    build.spec = @spec
772
    build.setup_signer
773
    open @gem, 'wb' do |gem_io|
774
      Gem::Package::TarWriter.new gem_io do |gem|
775
        build.add_metadata gem
776
        build.add_contents gem
777

  
778
        gem.add_file_simple 'a.sig', 0444, 0
779
        gem.add_file_simple 'a.sig', 0444, 0
780
      end
781
    end
782

  
783
    package = Gem::Package.new @gem
784

  
785
    e = assert_raises Gem::Security::Exception do
786
      package.verify
787
    end
788

  
789
    assert_equal 'duplicate files in the package: ("a.sig")', e.message
790
  end
791

  
726 792
  def test_verify_security_policy
727 793
    skip 'openssl is missing' unless defined?(OpenSSL::SSL)
728 794

  
......
780 846

  
781 847
        # write bogus data.tar.gz to foil signature
782 848
        bogus_data = Gem.gzip 'hello'
783
        gem.add_file_simple 'data.tar.gz', 0444, bogus_data.length do |io|
849
        fake_signer = Class.new do
850
          def digest_name; 'SHA512'; end
851
          def digest_algorithm; Digest(:SHA512); end
852
          def key; 'key'; end
853
          def sign(*); 'fake_sig'; end
854
        end
855
        gem.add_file_signed 'data2.tar.gz', 0444, fake_signer.new do |io|
784 856
          io.write bogus_data
785 857
        end
786 858

  
test/rubygems/test_gem_package_tar_header.rb
143 143
    assert_equal '012467', @tar_header.checksum
144 144
  end
145 145

  
146
  def test_from_bad_octal
147
    test_cases = [
148
      "00000006,44\000", # bogus character
149
      "00000006789\000", # non-octal digit
150
      "+0000001234\000", # positive sign
151
      "-0000001000\000", # negative sign
152
      "0x000123abc\000", # radix prefix
153
    ]
154

  
155
    test_cases.each do |val|
156
      header_s = @tar_header.to_s
157
      # overwrite the size field
158
      header_s[124, 12] = val
159
      io = TempIO.new header_s
160
      assert_raises ArgumentError do
161
        new_header = Gem::Package::TarHeader.from io
162
      end
163
      io.close! if io.respond_to? :close!
164
    end
165
  end
166

  
146 167
end
147 168

  
test/rubygems/test_gem_server.rb
336 336
    assert_match 'z 9', @res.body
337 337
  end
338 338

  
339

  
340
  def test_xss_homepage_fix_289313
341
    data = StringIO.new "GET / HTTP/1.0\r\n\r\n"
342
    dir = "#{@gemhome}2"
343

  
344
    spec = util_spec 'xsshomepagegem', 1
345
    spec.homepage = "javascript:confirm(document.domain)"
346

  
347
    specs_dir = File.join dir, 'specifications'
348
    FileUtils.mkdir_p specs_dir
349

  
350
    open File.join(specs_dir, spec.spec_name), 'w' do |io|
351
      io.write spec.to_ruby
352
    end
353

  
354
    server = Gem::Server.new dir, process_based_port, false
355

  
356
    @req.parse data
357

  
358
    server.root @req, @res
359

  
360
    assert_equal 200, @res.status
361
    assert_match 'xsshomepagegem 1', @res.body
362

  
363
    # This verifies that the homepage for this spec is not displayed and is set to ".", because it's not a 
364
    # valid HTTP/HTTPS URL and could be unsafe in an HTML context.  We would prefer to throw an exception here,
365
    # but spec.homepage is currently free form and not currently required to be a URL, this behavior may be 
366
    # validated in future versions of Gem::Specification.
367
    #
368
    # There are two variant we're checking here, one where rdoc is not present, and one where rdoc is present in the same regex:
369
    #
370
    # Variant #1 - rdoc not installed
371
    #
372
    #   <b>xsshomepagegem 1</b>
373
    #
374
    #
375
    #  <span title="rdoc not installed">[rdoc]</span>
376
    #
377
    #
378
    #
379
    #  <a href="." title=".">[www]</a>
380
    #
381
    # Variant #2 - rdoc installed
382
    #
383
    #   <b>xsshomepagegem 1</b>
384
    #
385
    #
386
    #  <a href="\/doc_root\/xsshomepagegem-1\/">\[rdoc\]<\/a>
387
    #
388
    #
389
    #
390
    #  <a href="." title=".">[www]</a>
391
    regex_match = /xsshomepagegem 1<\/b>[\n\s]+(<span title="rdoc not installed">\[rdoc\]<\/span>|<a href="\/doc_root\/xsshomepagegem-1\/">\[rdoc\]<\/a>)[\n\s]+<a href="\." title="\.">\[www\]<\/a>/
392
    assert_match regex_match, @res.body
393
  end
394

  
395
  def test_invalid_homepage
396
    data = StringIO.new "GET / HTTP/1.0\r\n\r\n"
397
    dir = "#{@gemhome}2"
398

  
399
    spec = util_spec 'invalidhomepagegem', 1
400
    spec.homepage = "notavalidhomepageurl"
401

  
402
    specs_dir = File.join dir, 'specifications'
403
    FileUtils.mkdir_p specs_dir
404

  
405
    open File.join(specs_dir, spec.spec_name), 'w' do |io|
406
      io.write spec.to_ruby
407
    end
408

  
409
    server = Gem::Server.new dir, process_based_port, false
410

  
411
    @req.parse data
412

  
413
    server.root @req, @res
414

  
415
    assert_equal 200, @res.status
416
    assert_match 'invalidhomepagegem 1', @res.body
417

  
418
    # This verifies that the homepage for this spec is not displayed and is set to ".", because it's not a 
419
    # valid HTTP/HTTPS URL and could be unsafe in an HTML context.  We would prefer to throw an exception here,
420
    # but spec.homepage is currently free form and not currently required to be a URL, this behavior may be 
421
    # validated in future versions of Gem::Specification.
422
    #
423
    # There are two variant we're checking here, one where rdoc is not present, and one where rdoc is present in the same regex:
424
    #
425
    # Variant #1 - rdoc not installed
426
    #
427
    #   <b>invalidhomepagegem 1</b>
428
    #
429
    #
430
    #  <span title="rdoc not installed">[rdoc]</span>
431
    #
432
    #
433
    #
434
    #  <a href="." title=".">[www]</a>
435
    #
436
    # Variant #2 - rdoc installed
437
    #
438
    #   <b>invalidhomepagegem 1</b>
439
    #
440
    #
441
    #  <a href="\/doc_root\/invalidhomepagegem-1\/">\[rdoc\]<\/a>
442
    #
443
    #
444
    #
445
    #  <a href="." title=".">[www]</a>
446
    regex_match = /invalidhomepagegem 1<\/b>[\n\s]+(<span title="rdoc not installed">\[rdoc\]<\/span>|<a href="\/doc_root\/invalidhomepagegem-1\/">\[rdoc\]<\/a>)[\n\s]+<a href="\." title="\.">\[www\]<\/a>/
447
    assert_match regex_match, @res.body
448
  end
449

  
450
  def test_valid_homepage_http
451
    data = StringIO.new "GET / HTTP/1.0\r\n\r\n"
452
    dir = "#{@gemhome}2"
453

  
454
    spec = util_spec 'validhomepagegemhttp', 1
455
    spec.homepage = "http://rubygems.org"
456

  
457
    specs_dir = File.join dir, 'specifications'
458
    FileUtils.mkdir_p specs_dir
459

  
460
    open File.join(specs_dir, spec.spec_name), 'w' do |io|
461
      io.write spec.to_ruby
462
    end
463

  
464
    server = Gem::Server.new dir, process_based_port, false
465

  
466
    @req.parse data
467

  
468
    server.root @req, @res
469

  
470
    assert_equal 200, @res.status
471
    assert_match 'validhomepagegemhttp 1', @res.body
472

  
473
    regex_match = /validhomepagegemhttp 1<\/b>[\n\s]+(<span title="rdoc not installed">\[rdoc\]<\/span>|<a href="\/doc_root\/validhomepagegemhttp-1\/">\[rdoc\]<\/a>)[\n\s]+<a href="http:\/\/rubygems\.org" title="http:\/\/rubygems\.org">\[www\]<\/a>/
474
    assert_match regex_match, @res.body
475
  end
476

  
477
  def test_valid_homepage_https
478
    data = StringIO.new "GET / HTTP/1.0\r\n\r\n"
479
    dir = "#{@gemhome}2"
480

  
481
    spec = util_spec 'validhomepagegemhttps', 1
482
    spec.homepage = "https://rubygems.org"
483

  
484
    specs_dir = File.join dir, 'specifications'
485
    FileUtils.mkdir_p specs_dir
486

  
487
    open File.join(specs_dir, spec.spec_name), 'w' do |io|
488
      io.write spec.to_ruby
489
    end
490

  
491
    server = Gem::Server.new dir, process_based_port, false
492

  
493
    @req.parse data
494

  
495
    server.root @req, @res
496

  
497
    assert_equal 200, @res.status
498
    assert_match 'validhomepagegemhttps 1', @res.body
499

  
500
    regex_match = /validhomepagegemhttps 1<\/b>[\n\s]+(<span title="rdoc not installed">\[rdoc\]<\/span>|<a href="\/doc_root\/validhomepagegemhttps-1\/">\[rdoc\]<\/a>)[\n\s]+<a href="https:\/\/rubygems\.org" title="https:\/\/rubygems\.org">\[www\]<\/a>/
501
    assert_match regex_match, @res.body
502
  end
503

  
339 504
  def test_specs
340 505
    data = StringIO.new "GET /specs.#{Gem.marshal_version} HTTP/1.0\r\n\r\n"
341 506
    @req.parse data
test/rubygems/test_gem_specification.rb
2890 2890
        @a1.validate
2891 2891
      end
2892 2892

  
2893
      assert_equal '"over at my cool site" is not a URI', e.message
2893
      assert_equal '"over at my cool site" is not a valid HTTP URI', e.message
2894

  
2895
      @a1.homepage = 'ftp://rubygems.org'
2896

  
2897
      e = assert_raises Gem::InvalidSpecificationException do
2898
        @a1.validate
2899
      end
2900

  
2901
      assert_equal '"ftp://rubygems.org" is not a valid HTTP URI', e.message
2902

  
2903
      @a1.homepage = 'http://rubygems.org'
2904
      assert_equal true, @a1.validate
2905

  
2906
      @a1.homepage = 'https://rubygems.org'
2907
      assert_equal true, @a1.validate
2908

  
2894 2909
    end
2895 2910
  end
2896 2911