Project

General

Profile

Bug #14481 ยป rubygems-276-for-ruby23.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.5.2.2'
13
  VERSION = '2.5.2.3'
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
376 376
            File.dirname destination
377 377
          end
378 378

  
379
        FileUtils.mkdir_p mkdir, mkdir_options
379
        mkdir_p_safe mkdir, mkdir_options, destination_dir, entry.full_name
380 380

  
381 381
        open destination, 'wb' do |out|
382 382
          out.write entry.read
......
414 414
    raise Gem::Package::PathError.new(filename, destination_dir) if
415 415
      filename.start_with? '/'
416 416

  
417
    destination_dir = File.realpath destination_dir if
418
      File.respond_to? :realpath
417
    destination_dir = realpath destination_dir
419 418
    destination_dir = File.expand_path destination_dir
420 419

  
421 420
    destination = File.join destination_dir, filename
422 421
    destination = File.expand_path destination
423 422

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

  
427 426
    destination.untaint
428 427
    destination
429 428
  end
430 429

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

  
431 446
  ##
432 447
  # Loads a Gem::Specification from the TarEntry +entry+
433 448

  
......
601 616
      raise Gem::Package::FormatError.new \
602 617
              'package content (data.tar.gz) is missing', @gem
603 618
    end
619

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

  
606 625
  ##
......
614 633
    raise Gem::Package::FormatError.new(e.message, entry.full_name)
615 634
  end
616 635

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

  
617 646
end
618 647

  
619 648
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
626 626
      executables = nil if executables.empty?
627 627
      executables.last["is_last"] = true if executables
628 628

  
629
      # Pre-process spec homepage for safety reasons
630
      begin
631
        homepage_uri = URI.parse(spec.homepage)
632
        if [URI::HTTP, URI::HTTPS].member? homepage_uri.class
633
          homepage_uri = spec.homepage
634
        else
635
          homepage_uri = "."
636
        end
637
      rescue URI::InvalidURIError
638
        homepage_uri = "."
639
      end
640

  
629 641
      specs << {
630 642
        "authors"             => spec.authors.sort.join(", "),
631 643
        "date"                => spec.date.to_s,
......
635 647
        "only_one_executable" => (executables && executables.size == 1),
636 648
        "full_name"           => spec.full_name,
637 649
        "has_deps"            => !deps.empty?,
638
        "homepage"            => spec.homepage,
650
        "homepage"            => homepage_uri,
639 651
        "name"                => spec.name,
640 652
        "rdoc_installed"      => Gem::RDoc.new(spec).rdoc_installed?,
641 653
        "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
......
2807 2808
      raise Gem::InvalidSpecificationException, "#{lazy} is not a summary"
2808 2809
    end
2809 2810

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

  
2816 2823
    # Warnings
test/rubygems/test_gem_commands_owner_command.rb
36 36
    assert_match %r{- user2@example.com}, @ui.output
37 37
  end
38 38

  
39
  def test_show_owners_dont_load_objects
40
    skip "testing a psych-only API" unless defined?(::Psych::DisallowedClass)
41

  
42
    response = <<EOF
43
---
44
- email: !ruby/object:Object {}
45
  id: 1
46
  handle: user1
47
- email: user2@example.com
48
- id: 3
49
  handle: user3
50
- id: 4
51
EOF
52

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

  
55
    assert_raises Psych::DisallowedClass do
56
      use_ui @ui do
57
        @cmd.show_owners("freewill")
58
      end
59
    end
60

  
61
  end
62

  
63

  
39 64
  def test_show_owners_setting_up_host_through_env_var
40 65
    response = "- email: user1@example.com\n"
41 66
    host = "http://rubygems.example"
test/rubygems/test_gem_package.rb
449 449
                 File.read(extracted)
450 450
  end
451 451

  
452
  def test_extract_symlink_parent
453
   skip 'symlink not supported' if Gem.win_platform?
454

  
455
   package = Gem::Package.new @gem
456

  
457
   tgz_io = util_tar_gz do |tar|
458
     tar.mkdir       'lib',               0755
459
     tar.add_symlink 'lib/link', '../..', 0644
460
     tar.add_file    'lib/link/outside.txt', 0644 do |io| io.write 'hi' end
461
   end
462

  
463
   # Extract into a subdirectory of @destination; if this test fails it writes
464
   # a file outside destination_subdir, but we want the file to remain inside
465
   # @destination so it will be cleaned up.
466
   destination_subdir = File.join @destination, 'subdir'
467
   FileUtils.mkdir_p destination_subdir
468

  
469
   e = assert_raises Gem::Package::PathError do
470
     package.extract_tar_gz tgz_io, destination_subdir
471
   end
472

  
473
   assert_equal("installing into parent path lib/link/outside.txt of " +
474
                 "#{destination_subdir} is not allowed", e.message)
475
  end
476

  
452 477
  def test_extract_tar_gz_directory
453 478
    package = Gem::Package.new @gem
454 479

  
......
560 585
                 "#{@destination} is not allowed", e.message)
561 586
  end
562 587

  
588
  def test_install_location_suffix
589
    package = Gem::Package.new @gem
590

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

  
593
    e = assert_raises Gem::Package::PathError do
594
      package.install_location filename, @destination
595
    end
596

  
597
    parent = File.expand_path File.join @destination, filename
598

  
599
    assert_equal("installing into parent path #{parent} of " +
600
                 "#{@destination} is not allowed", e.message)
601
  end
602

  
563 603
  def test_load_spec
564 604
    entry = StringIO.new Gem.gzip @spec.to_yaml
565 605
    def entry.full_name() 'metadata.gz' end
......
717 757
    assert_match %r%nonexistent.gem$%,           e.message
718 758
  end
719 759

  
760
  def test_verify_duplicate_file
761
    FileUtils.mkdir_p 'lib'
762
    FileUtils.touch 'lib/code.rb'
763

  
764
    build = Gem::Package.new @gem
765
    build.spec = @spec
766
    build.setup_signer
767
    open @gem, 'wb' do |gem_io|
768
      Gem::Package::TarWriter.new gem_io do |gem|
769
        build.add_metadata gem
770
        build.add_contents gem
771

  
772
        gem.add_file_simple 'a.sig', 0444, 0
773
        gem.add_file_simple 'a.sig', 0444, 0
774
      end
775
    end
776

  
777
    package = Gem::Package.new @gem
778

  
779
    e = assert_raises Gem::Security::Exception do
780
      package.verify
781
    end
782

  
783
    assert_equal 'duplicate files in the package: ("a.sig")', e.message
784
  end
785

  
720 786
  def test_verify_security_policy
721 787
    skip 'openssl is missing' unless defined?(OpenSSL::SSL)
722 788

  
......
774 840

  
775 841
        # write bogus data.tar.gz to foil signature
776 842
        bogus_data = Gem.gzip 'hello'
777
        gem.add_file_simple 'data.tar.gz', 0444, bogus_data.length do |io|
843
        fake_signer = Class.new do
844
          def digest_name; 'SHA512'; end
845
          def digest_algorithm; Digest(:SHA512); end
846
          def key; 'key'; end
847
          def sign(*); 'fake_sig'; end
848
        end
849
        gem.add_file_signed 'data2.tar.gz', 0444, fake_signer.new do |io|
778 850
          io.write bogus_data
779 851
        end
780 852

  
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
2879 2879
        @a1.validate
2880 2880
      end
2881 2881

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

  
2884
      @a1.homepage = 'ftp://rubygems.org'
2885

  
2886
      e = assert_raises Gem::InvalidSpecificationException do
2887
        @a1.validate
2888
      end
2889

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

  
2892
      @a1.homepage = 'http://rubygems.org'
2893
      assert_equal true, @a1.validate
2894

  
2895
      @a1.homepage = 'https://rubygems.org'
2896
      assert_equal true, @a1.validate
2897

  
2883 2898
    end
2884 2899
  end
2885 2900