minitar 0.5.4 → 0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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