minitar 0.5.4 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,12 @@
1
+ # coding: utf-8
2
+
3
+ require 'archive/tar/minitar'
4
+
5
+ if defined? ::Minitar
6
+ warn <<-EOS
7
+ ::Minitar is already defined.
8
+ This will conflict with future versions of minitar.
9
+ EOS
10
+ else
11
+ ::Minitar = Archive::Tar::Minitar
12
+ end
@@ -0,0 +1,11 @@
1
+ # -*- ruby encoding: utf-8 -*-
2
+
3
+ require 'fileutils'
4
+ require 'minitar'
5
+
6
+ gem 'minitest'
7
+ require 'minitest/autorun'
8
+
9
+ Dir.glob(File.join(File.dirname(__FILE__), 'support/*.rb')).each do |support|
10
+ require support
11
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TarTestHelpers
4
+ Field = Struct.new(:name, :offset, :length)
5
+ def self.Field(name, length) # rubocop:disable Style/MethodName
6
+ @offset ||= 0
7
+ field = Field.new(name, @offset, length)
8
+ @offset += length
9
+ FIELDS[name] = field
10
+ FIELD_ORDER << name
11
+ field
12
+ end
13
+
14
+ private
15
+
16
+ FIELDS = {} # rubocop:disable Style/MutableConstant
17
+ FIELD_ORDER = [] # rubocop:disable Style/MutableConstant
18
+
19
+ Field('name', 100)
20
+ Field('mode', 8)
21
+ Field('uid', 8)
22
+ Field('gid', 8)
23
+ Field('size', 12)
24
+ Field('mtime', 12)
25
+ Field('checksum', 8)
26
+ Field('typeflag', 1)
27
+ Field('linkname', 100)
28
+ Field('magic', 6)
29
+ Field('version', 2)
30
+ Field('uname', 32)
31
+ Field('gname', 32)
32
+ Field('devmajor', 8)
33
+ Field('devminor', 8)
34
+ Field('prefix', 155)
35
+
36
+ BLANK_CHECKSUM = ' ' * 8
37
+ NULL_100 = "\0" * 100
38
+ USTAR = "ustar\0".freeze
39
+ DOUBLE_ZERO = '00'.freeze
40
+
41
+ def assert_headers_equal(expected, actual)
42
+ FIELD_ORDER.each do |field|
43
+ message = if field == 'checksum'
44
+ 'Header checksums are expected to match.'
45
+ else
46
+ "Header field #{field} is expected to match."
47
+ end
48
+
49
+ offset = FIELDS[field].offset
50
+ length = FIELDS[field].length
51
+
52
+ assert_equal(expected[offset, length], actual[offset, length], message)
53
+ end
54
+ end
55
+
56
+ def assert_modes_equal(expected, actual, name)
57
+ return if Minitar.windows?
58
+
59
+ assert_equal(
60
+ mode_string(expected),
61
+ mode_string(actual),
62
+ "Mode for #{name} does not match"
63
+ )
64
+ end
65
+
66
+ def tar_file_header(fname, dname, mode, length)
67
+ update_checksum(header('0', fname, dname, length, mode))
68
+ end
69
+
70
+ def tar_dir_header(name, prefix, mode)
71
+ update_checksum(header('5', name, prefix, 0, mode))
72
+ end
73
+
74
+ def header(type, fname, dname, length, mode)
75
+ arr = [
76
+ asciiz(fname, 100), z(to_oct(mode, 7)), z(to_oct(nil, 7)),
77
+ z(to_oct(nil, 7)), z(to_oct(length, 11)), z(to_oct(0, 11)),
78
+ BLANK_CHECKSUM, type, NULL_100, USTAR, DOUBLE_ZERO, asciiz('', 32),
79
+ asciiz('', 32), z(to_oct(nil, 7)), z(to_oct(nil, 7)), asciiz(dname, 155)
80
+ ]
81
+ h = arr.join.bytes.to_a.pack('C100C8C8C8C12C12C8CC100C6C2C32C32C8C8C155')
82
+ ret = h + "\0" * (512 - h.size)
83
+ assert_equal(512, ret.size)
84
+ ret
85
+ end
86
+
87
+ def update_checksum(header)
88
+ header[FIELDS['checksum'].offset, FIELDS['checksum'].length] =
89
+ # inject(:+) was introduced in which version?
90
+ sp(z(to_oct(header.unpack('C*').inject { |a, e| a + e }, 6)))
91
+ header
92
+ end
93
+
94
+ def to_oct(n, pad_size)
95
+ if n.nil?
96
+ "\0" * pad_size
97
+ else
98
+ "%0#{pad_size}o" % n
99
+ end
100
+ end
101
+
102
+ def asciiz(str, length)
103
+ str + "\0" * (length - str.length)
104
+ end
105
+
106
+ def sp(s)
107
+ s + ' '
108
+ end
109
+
110
+ def z(s)
111
+ s + "\0"
112
+ end
113
+
114
+ def mode_string(value)
115
+ '%04o' % (value & 0o777)
116
+ end
117
+
118
+ Minitest::Test.send(:include, self)
119
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest_helper'
4
+
5
+ class TestTarHeader < Minitest::Test
6
+ def test_arguments_are_checked
7
+ ph = Archive::Tar::Minitar::PosixHeader
8
+ assert_raises(ArgumentError) {
9
+ ph.new(:name => '', :size => '', :mode => '')
10
+ }
11
+ assert_raises(ArgumentError) {
12
+ ph.new(:name => '', :size => '', :prefix => '')
13
+ }
14
+ assert_raises(ArgumentError) {
15
+ ph.new(:name => '', :prefix => '', :mode => '')
16
+ }
17
+ assert_raises(ArgumentError) {
18
+ ph.new(:prefix => '', :size => '', :mode => '')
19
+ }
20
+ end
21
+
22
+ def test_basic_headers
23
+ header = {
24
+ :name => 'bla',
25
+ :mode => 0o12345,
26
+ :size => 10,
27
+ :prefix => '',
28
+ :typeflag => '0'
29
+ }
30
+ assert_headers_equal(tar_file_header('bla', '', 0o12345, 10),
31
+ Archive::Tar::Minitar::PosixHeader.new(header).to_s)
32
+
33
+ header = {
34
+ :name => 'bla',
35
+ :mode => 0o12345,
36
+ :size => 0,
37
+ :prefix => '',
38
+ :typeflag => '5'
39
+ }
40
+ assert_headers_equal(tar_dir_header('bla', '', 0o12345),
41
+ Archive::Tar::Minitar::PosixHeader.new(header).to_s)
42
+ end
43
+
44
+ def test_long_name_works
45
+ header = {
46
+ :name => 'a' * 100, :mode => 0o12345, :size => 10, :prefix => ''
47
+ }
48
+ assert_headers_equal(tar_file_header('a' * 100, '', 0o12345, 10),
49
+ Archive::Tar::Minitar::PosixHeader.new(header).to_s)
50
+ header = {
51
+ :name => 'a' * 100, :mode => 0o12345, :size => 10, :prefix => 'bb' * 60
52
+ }
53
+ assert_headers_equal(tar_file_header('a' * 100, 'bb' * 60, 0o12345, 10),
54
+ Archive::Tar::Minitar::PosixHeader.new(header).to_s)
55
+ end
56
+
57
+ def test_from_stream
58
+ header = tar_file_header('a' * 100, '', 0o12345, 10)
59
+ header = StringIO.new(header)
60
+ h = Archive::Tar::Minitar::PosixHeader.from_stream(header)
61
+ assert_equal('a' * 100, h.name)
62
+ assert_equal(0o12345, h.mode)
63
+ assert_equal(10, h.size)
64
+ assert_equal('', h.prefix)
65
+ assert_equal('ustar', h.magic)
66
+ end
67
+
68
+ def test_from_stream_with_evil_name
69
+ header = tar_file_header("a \0" + "\0" * 97, '', 0o12345, 10)
70
+ header = StringIO.new(header)
71
+ h = Archive::Tar::Minitar::PosixHeader.from_stream header
72
+ assert_equal('a ', h.name)
73
+ end
74
+ end
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'minitar'
4
+ require 'minitest_helper'
5
+ require 'base64'
6
+ require 'zlib'
7
+
8
+ class TestTarInput < Minitest::Test
9
+ TEST_TGZ = Base64.decode64(<<-EOS).freeze
10
+ H4sIAKJpllQAA0tJLEnUK0ks0kuvYqAVMDAwMDMxUQDR5mbmYNrACMIHA2MjIwUDc3NzEzMz
11
+ QxMDAwUDQ2NTczMGBQOauQgJlBYDfQ90SiKQkZmHWx1QWVoaHnMgXlGA00MEyHdzMMzOnBbC
12
+ wPz28n2uJgOR44Xrq7tsHc/utNe/9FdihkmH3pZ7+zOTRFREzkzYJ99iHHDn4n0/Wb3E8Ceq
13
+ S0uOdSyMMg9Z+WVvX0vJucxs77vrvZf2arWcvHP9wa1Yp9lRnJmC59/P9+43PXum+tj7Ga+8
14
+ rtT+u3d941e765Y/bOrnvpv8X6jtz+wKqyk/v3n8P5xlO3l/1dn9q9Zotpy5funw/Of77Y/5
15
+ LVltz7ToTl7dXf5ppmf3n9p+PPxz/sz/qjZn9yf9Y4R7I2Ft3tqfPTUMGgMYlEMSpGXmpBrT
16
+ 2A5Qvjc1xZ3/DTDyv5GJmfFo/qcHCMnILFYAIlA6UDDWU+DlGmgXjYJRMApGwSgYBaNgFIyC
17
+ UTAKRsEoGAWjYBSMglEwCkbBKBgFo2AUjIJRMApGwSgYBaNgFIwCUgAAGnyo6wAoAAA=
18
+ EOS
19
+ FILETIMES = Time.utc(2004).to_i
20
+
21
+ TEST_CONTENTS = {
22
+ 'data.tar.gz' => { :size => 210, :mode => 0o644 },
23
+ 'file3' => { :size => 18, :mode => 0o755 }
24
+ }.freeze
25
+
26
+ TEST_DATA_CONTENTS = {
27
+ 'data/' => { :size => 0, :mode => 0o755 },
28
+ 'data/__dir__/' => { :size => 0, :mode => 0o755 },
29
+ 'data/file1' => { :size => 16, :mode => 0o644 },
30
+ 'data/file2' => { :size => 16, :mode => 0o644 }
31
+ }.freeze
32
+
33
+ def setup
34
+ FileUtils.mkdir_p('data__')
35
+ end
36
+
37
+ def teardown
38
+ FileUtils.rm_rf('data__')
39
+ end
40
+
41
+ def test_each_works
42
+ reader = Zlib::GzipReader.new(StringIO.new(TEST_TGZ))
43
+ Minitar::Input.open(reader) do |stream|
44
+ outer = 0
45
+ stream.each.with_index do |entry, i|
46
+ assert_kind_of(Minitar::Reader::EntryStream, entry)
47
+ assert TEST_CONTENTS.key?(entry.name)
48
+
49
+ assert_equal(TEST_CONTENTS[entry.name][:size], entry.size, entry.name)
50
+ assert_modes_equal(TEST_CONTENTS[entry.name][:mode],
51
+ entry.mode, entry.name)
52
+ assert_equal(FILETIMES, entry.mtime, 'entry.mtime')
53
+
54
+ if i.zero?
55
+ data_reader = Zlib::GzipReader.new(StringIO.new(entry.read))
56
+ Minitar::Input.open(data_reader) do |is2|
57
+ inner = 0
58
+ is2.each_with_index do |entry2, _j|
59
+ assert_kind_of(Minitar::Reader::EntryStream, entry2)
60
+ assert TEST_DATA_CONTENTS.key?(entry2.name)
61
+ assert_equal(TEST_DATA_CONTENTS[entry2.name][:size], entry2.size,
62
+ entry2.name)
63
+ assert_modes_equal(TEST_DATA_CONTENTS[entry2.name][:mode],
64
+ entry2.mode, entry2.name)
65
+ assert_equal(FILETIMES, entry2.mtime, entry2.name)
66
+ inner += 1
67
+ end
68
+ assert_equal(4, inner)
69
+ end
70
+ end
71
+
72
+ outer += 1
73
+ end
74
+
75
+ assert_equal(2, outer)
76
+ end
77
+ end
78
+
79
+ def test_extract_entry_works
80
+ reader = Zlib::GzipReader.new(StringIO.new(TEST_TGZ))
81
+ Minitar::Input.open(reader) do |stream|
82
+ outer_count = 0
83
+ stream.each_with_index do |entry, i|
84
+ stream.extract_entry('data__', entry)
85
+ name = File.join('data__', entry.name)
86
+
87
+ assert TEST_CONTENTS.key?(entry.name)
88
+
89
+ if entry.directory?
90
+ assert(File.directory?(name))
91
+ else
92
+ assert(File.file?(name))
93
+
94
+ assert_equal(TEST_CONTENTS[entry.name][:size], File.stat(name).size)
95
+ end
96
+
97
+ assert_modes_equal(TEST_CONTENTS[entry.name][:mode],
98
+ File.stat(name).mode, entry.name)
99
+
100
+ if i.zero?
101
+ begin
102
+ ff = File.open(name, 'rb')
103
+ data_reader = Zlib::GzipReader.new(ff)
104
+ Minitar::Input.open(data_reader) do |is2|
105
+ is2.each_with_index do |entry2, _j|
106
+ is2.extract_entry('data__', entry2)
107
+ name2 = File.join('data__', entry2.name)
108
+
109
+ assert TEST_DATA_CONTENTS.key?(entry2.name)
110
+
111
+ if entry2.directory?
112
+ assert(File.directory?(name2))
113
+ else
114
+ assert(File.file?(name2))
115
+ assert_equal(TEST_DATA_CONTENTS[entry2.name][:size],
116
+ File.stat(name2).size)
117
+ end
118
+ assert_modes_equal(TEST_DATA_CONTENTS[entry2.name][:mode],
119
+ File.stat(name2).mode, name2)
120
+ end
121
+ end
122
+ ensure
123
+ ff.close unless ff.closed?
124
+ end
125
+ end
126
+
127
+ outer_count += 1
128
+ end
129
+
130
+ assert_equal(2, outer_count)
131
+ end
132
+ end
133
+
134
+ def test_extract_entry_breaks_symlinks
135
+ return if Minitar.windows?
136
+
137
+ IO.respond_to?(:write) &&
138
+ IO.write('data__/file4', '') ||
139
+ File.open('data__/file4', 'w') { |f| f.write '' }
140
+
141
+ File.symlink('data__/file4', 'data__/file3')
142
+ File.symlink('data__/file4', 'data__/data')
143
+
144
+ Minitar.unpack(Zlib::GzipReader.new(StringIO.new(TEST_TGZ)), 'data__')
145
+ Minitar.unpack(Zlib::GzipReader.new(File.open('data__/data.tar.gz', 'rb')),
146
+ 'data__')
147
+
148
+ refute File.symlink?('data__/file3')
149
+ refute File.symlink?('data__/data')
150
+ end
151
+
152
+ RELATIVE_DIRECTORY_TGZ = Base64.decode64 <<-EOS
153
+ H4sICIIoKVgCA2JhZC1kaXIudGFyANPT0y8sTy0qqWSgHTAwMDAzMVEA0eZmpmDawAjChwEFQ2MDQyMg
154
+ MDUzVDAwNDY0N2VQMGCgAygtLkksAjolEcjIzMOtDqgsLQ2/J0H+gNOjYBSMglEwyAEA2LchrwAGAAA=
155
+ EOS
156
+
157
+ def test_extract_entry_fails_with_relative_directory
158
+ reader = Zlib::GzipReader.new(StringIO.new(RELATIVE_DIRECTORY_TGZ))
159
+ Minitar::Input.open(reader) do |stream|
160
+ stream.each do |entry|
161
+ assert_raises Archive::Tar::Minitar::SecureRelativePathError do
162
+ stream.extract_entry('data__', entry)
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'minitar'
4
+ require 'minitest_helper'
5
+
6
+ class TestTarOutput < Minitest::Test
7
+ def setup
8
+ FileUtils.mkdir_p('data__')
9
+ %w(a b c).each do |filename|
10
+ name = File.join('data__', filename)
11
+ File.open(name, 'wb') { |f|
12
+ f.puts "#{name}: 123456789012345678901234567890"
13
+ }
14
+ end
15
+ @tarfile = 'data__/bla2.tar'
16
+ end
17
+
18
+ def teardown
19
+ FileUtils.rm_rf('data__')
20
+ end
21
+
22
+ def test_file_looks_good
23
+ Minitar::Output.open(@tarfile) do |os|
24
+ Dir.chdir('data__') do
25
+ %w(a b c).each do |name|
26
+ stat = File.stat(name)
27
+ opts = { :size => stat.size, :mode => 0o644 }
28
+ os.tar.add_file_simple(name, opts) do |ss|
29
+ File.open(name, 'rb') { |ff| ss.write(ff.read(4096)) until ff.eof? }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ ff = File.open(@tarfile, 'rb')
35
+ Minitar::Reader.open(ff) do |is|
36
+ ii = 0
37
+ is.each do |entry|
38
+ case ii
39
+ when 0
40
+ assert_equal('a', entry.name)
41
+ when 1
42
+ assert_equal('b', entry.name)
43
+ when 2
44
+ assert_equal('c', entry.name)
45
+ end
46
+ ii += 1
47
+ end
48
+ assert_equal(3, ii)
49
+ end
50
+ ensure
51
+ ff.close if ff
52
+ end
53
+ end
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'minitar'
4
+ require 'minitest_helper'
5
+
6
+ class TestTarReader < Minitest::Test
7
+ def test_multiple_entries
8
+ str = tar_file_header('lib/foo', '', 0o10644, 10) + "\0" * 512
9
+ str += tar_file_header('bar', 'baz', 0o644, 0)
10
+ str += tar_dir_header('foo', 'bar', 0o12345)
11
+ str += "\0" * 1024
12
+ names = %w(lib/foo bar foo)
13
+ prefixes = ['', 'baz', 'bar']
14
+ modes = [0o10644, 0o644, 0o12345]
15
+ sizes = [10, 0, 0]
16
+ isdir = [false, false, true]
17
+ isfile = [true, true, false]
18
+ Minitar::Reader.new(StringIO.new(str)) do |is|
19
+ i = 0
20
+ is.each_entry do |entry|
21
+ assert_kind_of(Minitar::Reader::EntryStream, entry)
22
+ assert_equal(names[i], entry.name)
23
+ assert_equal(prefixes[i], entry.prefix)
24
+ assert_equal(sizes[i], entry.size)
25
+ assert_equal(modes[i], entry.mode)
26
+ assert_equal(isdir[i], entry.directory?)
27
+ assert_equal(isfile[i], entry.file?)
28
+ if prefixes[i] != ''
29
+ assert_equal(File.join(prefixes[i], names[i]), entry.full_name)
30
+ else
31
+ assert_equal(names[i], entry.name)
32
+ end
33
+ i += 1
34
+ end
35
+ assert_equal(names.size, i)
36
+ end
37
+ end
38
+
39
+ def test_rewind_entry_works
40
+ content = ('a'..'z').to_a.join(' ')
41
+ str = tar_file_header('lib/foo', '', 0o10644, content.size) + content +
42
+ "\0" * (512 - content.size)
43
+ str << "\0" * 1024
44
+ Minitar::Reader.new(StringIO.new(str)) do |is|
45
+ is.each_entry do |entry|
46
+ 3.times do
47
+ entry.rewind
48
+ assert_equal(content, entry.read)
49
+ assert_equal(content.size, entry.pos)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def test_rewind_works
56
+ content = ('a'..'z').to_a.join(' ')
57
+ str = tar_file_header('lib/foo', '', 0o10644, content.size) + content +
58
+ "\0" * (512 - content.size)
59
+ str << "\0" * 1024
60
+ Minitar::Reader.new(StringIO.new(str)) do |is|
61
+ 3.times do
62
+ is.rewind
63
+ i = 0
64
+ is.each_entry do |entry|
65
+ assert_equal(content, entry.read)
66
+ i += 1
67
+ end
68
+ assert_equal(1, i)
69
+ end
70
+ end
71
+ end
72
+
73
+ def test_read_works
74
+ contents = ('a'..'z').inject('') { |a, e| a << e * 100 }
75
+ str = tar_file_header('lib/foo', '', 0o10644, contents.size) + contents
76
+ str += "\0" * (512 - (str.size % 512))
77
+ Minitar::Reader.new(StringIO.new(str)) do |is|
78
+ is.each_entry do |entry|
79
+ assert_kind_of(Minitar::Reader::EntryStream, entry)
80
+ data = entry.read(3000) # bigger than contents.size
81
+ assert_equal(contents, data)
82
+ assert_equal(true, entry.eof?)
83
+ end
84
+ end
85
+ Minitar::Reader.new(StringIO.new(str)) do |is|
86
+ is.each_entry do |entry|
87
+ assert_kind_of(Minitar::Reader::EntryStream, entry)
88
+ data = entry.read(100)
89
+ (entry.size - data.size).times { data << entry.getc.chr }
90
+ assert_equal(contents, data)
91
+ assert_equal(nil, entry.read(10))
92
+ assert_equal(true, entry.eof?)
93
+ end
94
+ end
95
+ Minitar::Reader.new(StringIO.new(str)) do |is|
96
+ is.each_entry do |entry|
97
+ assert_kind_of(Minitar::Reader::EntryStream, entry)
98
+ data = entry.read
99
+ assert_equal(contents, data)
100
+ assert_equal(nil, entry.read(10))
101
+ assert_equal(nil, entry.read)
102
+ assert_equal(nil, entry.getc)
103
+ assert_equal(true, entry.eof?)
104
+ end
105
+ end
106
+ end
107
+
108
+ def test_eof_works
109
+ str = tar_file_header('bar', 'baz', 0o644, 0)
110
+ Minitar::Reader.new(StringIO.new(str)) do |is|
111
+ is.each_entry do |entry|
112
+ assert_kind_of(Minitar::Reader::EntryStream, entry)
113
+ data = entry.read
114
+ assert_equal(nil, data)
115
+ assert_equal(nil, entry.read(10))
116
+ assert_equal(nil, entry.read)
117
+ assert_equal(nil, entry.getc)
118
+ assert_equal(true, entry.eof?)
119
+ end
120
+ end
121
+ str = tar_dir_header('foo', 'bar', 0o12345)
122
+ Minitar::Reader.new(StringIO.new(str)) do |is|
123
+ is.each_entry do |entry|
124
+ assert_kind_of(Minitar::Reader::EntryStream, entry)
125
+ data = entry.read
126
+ assert_equal(nil, data)
127
+ assert_equal(nil, entry.read(10))
128
+ assert_equal(nil, entry.read)
129
+ assert_equal(nil, entry.getc)
130
+ assert_equal(true, entry.eof?)
131
+ end
132
+ end
133
+ str = tar_dir_header('foo', 'bar', 0o12345)
134
+ str += tar_file_header('bar', 'baz', 0o644, 0)
135
+ str += tar_file_header('bar', 'baz', 0o644, 0)
136
+ Minitar::Reader.new(StringIO.new(str)) do |is|
137
+ is.each_entry do |entry|
138
+ assert_kind_of(Minitar::Reader::EntryStream, entry)
139
+ data = entry.read
140
+ assert_equal(nil, data)
141
+ assert_equal(nil, entry.read(10))
142
+ assert_equal(nil, entry.read)
143
+ assert_equal(nil, entry.getc)
144
+ assert_equal(true, entry.eof?)
145
+ end
146
+ end
147
+ end
148
+ end