rubyzip 1.1.4 → 1.1.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubyzip might be problematic. Click here for more details.

Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/lib/zip/file.rb +2 -1
  3. data/lib/zip/inflater.rb +15 -44
  4. data/lib/zip/ioextras/abstract_input_stream.rb +5 -1
  5. data/lib/zip/version.rb +1 -1
  6. data/test/alltests.rb +18 -0
  7. data/test/basic_zip_file_test.rb +64 -0
  8. data/test/central_directory_entry_test.rb +73 -0
  9. data/test/central_directory_test.rb +100 -0
  10. data/test/data/file1.txt +46 -0
  11. data/test/data/file1.txt.deflatedData +0 -0
  12. data/test/data/file2.txt +1504 -0
  13. data/test/data/file2.txt.other +0 -0
  14. data/test/data/globTest.zip +0 -0
  15. data/test/data/globTest/foo.txt +0 -0
  16. data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
  17. data/test/data/globTest/food.txt +0 -0
  18. data/test/data/mimetype +1 -0
  19. data/test/data/notzippedruby.rb +7 -0
  20. data/test/data/rubycode.zip +0 -0
  21. data/test/data/rubycode2.zip +0 -0
  22. data/test/data/testDirectory.bin +0 -0
  23. data/test/data/zip64-sample.zip +0 -0
  24. data/test/data/zipWithDirs.zip +0 -0
  25. data/test/deflater_test.rb +62 -0
  26. data/test/dummy.txt +1 -0
  27. data/test/entry_set_test.rb +125 -0
  28. data/test/entry_test.rb +165 -0
  29. data/test/errors_test.rb +36 -0
  30. data/test/extra_field_test.rb +69 -0
  31. data/test/file_extract_directory_test.rb +55 -0
  32. data/test/file_extract_test.rb +90 -0
  33. data/test/file_split_test.rb +60 -0
  34. data/test/file_test.rb +568 -0
  35. data/test/filesystem/dir_iterator_test.rb +62 -0
  36. data/test/filesystem/directory_test.rb +131 -0
  37. data/test/filesystem/file_mutating_test.rb +100 -0
  38. data/test/filesystem/file_nonmutating_test.rb +505 -0
  39. data/test/filesystem/file_stat_test.rb +66 -0
  40. data/test/gentestfiles.rb +134 -0
  41. data/test/inflater_test.rb +14 -0
  42. data/test/input_stream_test.rb +170 -0
  43. data/test/ioextras/abstract_input_stream_test.rb +103 -0
  44. data/test/ioextras/abstract_output_stream_test.rb +106 -0
  45. data/test/ioextras/fake_io_test.rb +18 -0
  46. data/test/ioextrastest.rb +233 -0
  47. data/test/local_entry_test.rb +153 -0
  48. data/test/odt_test_fix.rb +30 -0
  49. data/test/output_stream_test.rb +114 -0
  50. data/test/pass_thru_compressor_test.rb +31 -0
  51. data/test/pass_thru_decompressor_test.rb +15 -0
  52. data/test/sample.odt +0 -0
  53. data/test/settings_test.rb +71 -0
  54. data/test/test_helper.rb +228 -0
  55. data/test/unicode_file_names_and_comments_test.rb +40 -0
  56. data/test/zip64_full_test.rb +49 -0
  57. data/test/zip64_support_test.rb +15 -0
  58. metadata +107 -3
@@ -0,0 +1,66 @@
1
+ require 'test_helper'
2
+ require 'zip/filesystem'
3
+
4
+ class ZipFsFileStatTest < MiniTest::Unit::TestCase
5
+
6
+ def setup
7
+ @zip_file = ::Zip::File.new("test/data/zipWithDirs.zip")
8
+ end
9
+
10
+ def teardown
11
+ @zip_file.close if @zip_file
12
+ end
13
+
14
+ def test_blocks
15
+ assert_equal(nil, @zip_file.file.stat("file1").blocks)
16
+ end
17
+
18
+ def test_ino
19
+ assert_equal(0, @zip_file.file.stat("file1").ino)
20
+ end
21
+
22
+ def test_uid
23
+ assert_equal(0, @zip_file.file.stat("file1").uid)
24
+ end
25
+
26
+ def test_gid
27
+ assert_equal(0, @zip_file.file.stat("file1").gid)
28
+ end
29
+
30
+ def test_ftype
31
+ assert_equal("file", @zip_file.file.stat("file1").ftype)
32
+ assert_equal("directory", @zip_file.file.stat("dir1").ftype)
33
+ end
34
+
35
+ def test_mode
36
+ assert_equal(0600, @zip_file.file.stat("file1").mode & 0777)
37
+ assert_equal(0600, @zip_file.file.stat("file1").mode & 0777)
38
+ assert_equal(0755, @zip_file.file.stat("dir1").mode & 0777)
39
+ assert_equal(0755, @zip_file.file.stat("dir1").mode & 0777)
40
+ end
41
+
42
+ def test_dev
43
+ assert_equal(0, @zip_file.file.stat("file1").dev)
44
+ end
45
+
46
+ def test_rdev
47
+ assert_equal(0, @zip_file.file.stat("file1").rdev)
48
+ end
49
+
50
+ def test_rdev_major
51
+ assert_equal(0, @zip_file.file.stat("file1").rdev_major)
52
+ end
53
+
54
+ def test_rdev_minor
55
+ assert_equal(0, @zip_file.file.stat("file1").rdev_minor)
56
+ end
57
+
58
+ def test_nlink
59
+ assert_equal(1, @zip_file.file.stat("file1").nlink)
60
+ end
61
+
62
+ def test_blksize
63
+ assert_nil(@zip_file.file.stat("file1").blksize)
64
+ end
65
+
66
+ end
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $VERBOSE = true
4
+
5
+ class TestFiles
6
+ RANDOM_ASCII_FILE1 = "test/data/generated/randomAscii1.txt"
7
+ RANDOM_ASCII_FILE2 = "test/data/generated/randomAscii2.txt"
8
+ RANDOM_ASCII_FILE3 = "test/data/generated/randomAscii3.txt"
9
+ RANDOM_BINARY_FILE1 = "test/data/generated/randomBinary1.bin"
10
+ RANDOM_BINARY_FILE2 = "test/data/generated/randomBinary2.bin"
11
+
12
+ EMPTY_TEST_DIR = "test/data/generated/emptytestdir"
13
+
14
+ ASCII_TEST_FILES = [RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3]
15
+ BINARY_TEST_FILES = [RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2]
16
+ TEST_DIRECTORIES = [EMPTY_TEST_DIR]
17
+ TEST_FILES = [ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR].flatten!
18
+
19
+ class << self
20
+ def create_test_files
21
+ Dir.mkdir "test/data/generated" unless Dir.exist?('test/data/generated')
22
+
23
+ ASCII_TEST_FILES.each_with_index do |filename, index|
24
+ create_random_ascii(filename, 1E4 * (index+1))
25
+ end
26
+
27
+ BINARY_TEST_FILES.each_with_index do |filename, index|
28
+ create_random_binary(filename, 1E4 * (index+1))
29
+ end
30
+
31
+ ensure_dir(EMPTY_TEST_DIR)
32
+ end
33
+
34
+ private
35
+
36
+ def create_random_ascii(filename, size)
37
+ File.open(filename, "wb") do |file|
38
+ while (file.tell < size)
39
+ file << rand
40
+ end
41
+ end
42
+ end
43
+
44
+ def create_random_binary(filename, size)
45
+ File.open(filename, "wb") do |file|
46
+ while (file.tell < size)
47
+ file << [rand].pack("V")
48
+ end
49
+ end
50
+ end
51
+
52
+ def ensure_dir(name)
53
+ if File.exist?(name)
54
+ return if File.stat(name).directory?
55
+ File.delete(name)
56
+ end
57
+ Dir.mkdir(name)
58
+ end
59
+
60
+ end
61
+ end
62
+
63
+
64
+ # For representation and creation of
65
+ # test data
66
+ class TestZipFile
67
+ attr_accessor :zip_name, :entry_names, :comment
68
+
69
+ def initialize(zip_name, entry_names, comment = "")
70
+ @zip_name=zip_name
71
+ @entry_names=entry_names
72
+ @comment = comment
73
+ end
74
+
75
+ def TestZipFile.create_test_zips
76
+ raise "failed to create test zip '#{TEST_ZIP1.zip_name}'" unless system("/usr/bin/zip #{TEST_ZIP1.zip_name} test/data/file2.txt")
77
+ raise "failed to remove entry from '#{TEST_ZIP1.zip_name}'" unless system("/usr/bin/zip #{TEST_ZIP1.zip_name} -d test/data/file2.txt")
78
+
79
+ File.open("test/data/generated/empty.txt", "w") {}
80
+ File.open("test/data/generated/empty_chmod640.txt", "w") {}
81
+ ::File.chmod(0640, "test/data/generated/empty_chmod640.txt")
82
+
83
+ File.open("test/data/generated/short.txt", "w") { |file| file << "ABCDEF" }
84
+ ziptestTxt=""
85
+ File.open("test/data/file2.txt") { |file| ziptestTxt=file.read }
86
+ File.open("test/data/generated/longAscii.txt", "w") do |file|
87
+ while (file.tell < 1E5)
88
+ file << ziptestTxt
89
+ end
90
+ end
91
+
92
+ testBinaryPattern=""
93
+ File.open("test/data/generated/empty.zip") { |file| testBinaryPattern=file.read }
94
+ testBinaryPattern *= 4
95
+
96
+ File.open("test/data/generated/longBinary.bin", "wb") do |file|
97
+ while (file.tell < 6E5)
98
+ file << testBinaryPattern << rand << "\0"
99
+ end
100
+ end
101
+
102
+ raise "failed to create test zip '#{TEST_ZIP2.zip_name}'" unless system("/usr/bin/zip #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.join(' ')}")
103
+
104
+ if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
105
+ raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" unless system("echo #{TEST_ZIP2.comment}| /usr/bin/zip -z #{TEST_ZIP2.zip_name}\"")
106
+ else
107
+ # without bash system interprets everything after echo as parameters to
108
+ # echo including | zip -z ...
109
+ raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" unless system("bash -c \"echo #{TEST_ZIP2.comment} | /usr/bin/zip -z #{TEST_ZIP2.zip_name}\"")
110
+ end
111
+
112
+ raise "failed to create test zip '#{TEST_ZIP3.zip_name}'" unless system("/usr/bin/zip #{TEST_ZIP3.zip_name} #{TEST_ZIP3.entry_names.join(' ')}")
113
+
114
+ raise "failed to create test zip '#{TEST_ZIP4.zip_name}'" unless system("/usr/bin/zip #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.join(' ')}")
115
+ rescue
116
+ # If there are any Windows developers wanting to use a command line zip.exe
117
+ # to help create the following files, there's a free one available from
118
+ # http://stahlworks.com/dev/index.php?tool=zipunzip
119
+ # that works with the above code
120
+ raise $!.to_s +
121
+ "\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" +
122
+ "to create test data. If you don't have it you can download\n" +
123
+ "the necessary test files at http://sf.net/projects/rubyzip."
124
+ end
125
+
126
+ TEST_ZIP1 = TestZipFile.new("test/data/generated/empty.zip", [])
127
+ TEST_ZIP2 = TestZipFile.new("test/data/generated/5entry.zip", %w{ test/data/generated/longAscii.txt test/data/generated/empty.txt test/data/generated/empty_chmod640.txt test/data/generated/short.txt test/data/generated/longBinary.bin},
128
+ "my zip comment")
129
+ TEST_ZIP3 = TestZipFile.new("test/data/generated/test1.zip", %w{ test/data/file1.txt })
130
+ TEST_ZIP4 = TestZipFile.new("test/data/generated/zipWithDir.zip", ["test/data/file1.txt",
131
+ TestFiles::EMPTY_TEST_DIR])
132
+ end
133
+
134
+
@@ -0,0 +1,14 @@
1
+ require 'test_helper'
2
+ class InflaterTest < MiniTest::Unit::TestCase
3
+ include DecompressorTests
4
+
5
+ def setup
6
+ super
7
+ @file = File.new("test/data/file1.txt.deflatedData", "rb")
8
+ @decompressor = ::Zip::Inflater.new(@file)
9
+ end
10
+
11
+ def teardown
12
+ @file.close
13
+ end
14
+ end
@@ -0,0 +1,170 @@
1
+ require 'test_helper'
2
+
3
+ class ZipInputStreamTest < MiniTest::Unit::TestCase
4
+ include AssertEntry
5
+
6
+ def test_new
7
+ zis = ::Zip::InputStream.new(TestZipFile::TEST_ZIP2.zip_name)
8
+ assert_stream_contents(zis, TestZipFile::TEST_ZIP2)
9
+ assert_equal(true, zis.eof?)
10
+ zis.close
11
+ end
12
+
13
+ def test_openWithBlock
14
+ ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) {
15
+ |zis|
16
+ assert_stream_contents(zis, TestZipFile::TEST_ZIP2)
17
+ assert_equal(true, zis.eof?)
18
+ }
19
+ end
20
+
21
+ def test_openWithoutBlock
22
+ zis = ::Zip::InputStream.open(File.new(TestZipFile::TEST_ZIP2.zip_name, "rb"))
23
+ assert_stream_contents(zis, TestZipFile::TEST_ZIP2)
24
+ end
25
+
26
+ def test_openBufferWithBlock
27
+ ::Zip::InputStream.open(File.new(TestZipFile::TEST_ZIP2.zip_name, "rb")) do |zis|
28
+ assert_stream_contents(zis, TestZipFile::TEST_ZIP2)
29
+ assert_equal(true, zis.eof?)
30
+ end
31
+ end
32
+
33
+ def test_open_string_io_without_block
34
+ string_io = ::StringIO.new(::File.read(TestZipFile::TEST_ZIP2.zip_name))
35
+ zis = ::Zip::InputStream.open(string_io)
36
+ assert_stream_contents(zis, TestZipFile::TEST_ZIP2)
37
+ end
38
+
39
+ def test_open_string_io_with_block
40
+ string_io = ::StringIO.new(::File.read(TestZipFile::TEST_ZIP2.zip_name))
41
+ ::Zip::InputStream.open(string_io) do |zis|
42
+ assert_stream_contents(zis, TestZipFile::TEST_ZIP2)
43
+ assert_equal(true, zis.eof?)
44
+ end
45
+ end
46
+
47
+ def test_openBufferWithoutBlock
48
+ zis = ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name)
49
+ assert_stream_contents(zis, TestZipFile::TEST_ZIP2)
50
+ end
51
+
52
+ def test_incompleteReads
53
+ ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) {
54
+ |zis|
55
+ entry = zis.get_next_entry # longAscii.txt
56
+ assert_equal(false, zis.eof?)
57
+ assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], entry.name)
58
+ assert zis.gets.length > 0
59
+ assert_equal(false, zis.eof?)
60
+ entry = zis.get_next_entry # empty.txt
61
+ assert_equal(TestZipFile::TEST_ZIP2.entry_names[1], entry.name)
62
+ assert_equal(0, entry.size)
63
+ assert_equal(nil, zis.gets)
64
+ assert_equal(true, zis.eof?)
65
+ entry = zis.get_next_entry # empty_chmod640.txt
66
+ assert_equal(TestZipFile::TEST_ZIP2.entry_names[2], entry.name)
67
+ assert_equal(0, entry.size)
68
+ assert_equal(nil, zis.gets)
69
+ assert_equal(true, zis.eof?)
70
+ entry = zis.get_next_entry # short.txt
71
+ assert_equal(TestZipFile::TEST_ZIP2.entry_names[3], entry.name)
72
+ assert zis.gets.length > 0
73
+ entry = zis.get_next_entry # longBinary.bin
74
+ assert_equal(TestZipFile::TEST_ZIP2.entry_names[4], entry.name)
75
+ assert zis.gets.length > 0
76
+ }
77
+ end
78
+
79
+ def test_incomplete_reads_from_string_io
80
+ string_io = ::StringIO.new(::File.read(TestZipFile::TEST_ZIP2.zip_name))
81
+ ::Zip::InputStream.open(string_io) do |zis|
82
+ entry = zis.get_next_entry # longAscii.txt
83
+ assert_equal(false, zis.eof?)
84
+ assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], entry.name)
85
+ assert zis.gets.length > 0
86
+ assert_equal(false, zis.eof?)
87
+ entry = zis.get_next_entry # empty.txt
88
+ assert_equal(TestZipFile::TEST_ZIP2.entry_names[1], entry.name)
89
+ assert_equal(0, entry.size)
90
+ assert_equal(nil, zis.gets)
91
+ assert_equal(true, zis.eof?)
92
+ entry = zis.get_next_entry # empty_chmod640.txt
93
+ assert_equal(TestZipFile::TEST_ZIP2.entry_names[2], entry.name)
94
+ assert_equal(0, entry.size)
95
+ assert_equal(nil, zis.gets)
96
+ assert_equal(true, zis.eof?)
97
+ entry = zis.get_next_entry # short.txt
98
+ assert_equal(TestZipFile::TEST_ZIP2.entry_names[3], entry.name)
99
+ assert zis.gets.length > 0
100
+ entry = zis.get_next_entry # longBinary.bin
101
+ assert_equal(TestZipFile::TEST_ZIP2.entry_names[4], entry.name)
102
+ assert zis.gets.length > 0
103
+ end
104
+ end
105
+
106
+ def test_read_with_number_of_bytes_returns_nil_at_eof
107
+ ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) do |zis|
108
+ entry = zis.get_next_entry # longAscii.txt
109
+ zis.read(entry.size)
110
+ assert_equal(true, zis.eof?)
111
+ assert_nil(zis.read(1))
112
+ assert_nil(zis.read(1))
113
+ end
114
+ end
115
+
116
+ def test_rewind
117
+ ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) {
118
+ |zis|
119
+ e = zis.get_next_entry
120
+ assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], e.name)
121
+
122
+ # Do a little reading
123
+ buf = ""
124
+ buf << zis.read(100)
125
+ assert_equal(100, zis.pos)
126
+ buf << (zis.gets || "")
127
+ buf << (zis.gets || "")
128
+ assert_equal(false, zis.eof?)
129
+
130
+ zis.rewind
131
+
132
+ buf2 = ""
133
+ buf2 << zis.read(100)
134
+ buf2 << (zis.gets || "")
135
+ buf2 << (zis.gets || "")
136
+
137
+ assert_equal(buf, buf2)
138
+
139
+ zis.rewind
140
+ assert_equal(false, zis.eof?)
141
+ assert_equal(0, zis.pos)
142
+
143
+ assert_entry(e.name, zis, e.name)
144
+ }
145
+ end
146
+
147
+ def test_mix_read_and_gets
148
+ ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) {
149
+ |zis|
150
+ zis.get_next_entry
151
+ assert_equal("#!/usr/bin/env ruby", zis.gets.chomp)
152
+ assert_equal(false, zis.eof?)
153
+ assert_equal("", zis.gets.chomp)
154
+ assert_equal(false, zis.eof?)
155
+ assert_equal("$VERBOSE =", zis.read(10))
156
+ assert_equal(false, zis.eof?)
157
+ }
158
+ end
159
+
160
+ def test_ungetc
161
+ ::Zip::InputStream.open(TestZipFile::TEST_ZIP2.zip_name) do |zis|
162
+ zis.get_next_entry
163
+ first_line = zis.gets.chomp
164
+ first_line.reverse.bytes.each { |b| zis.ungetc(b) }
165
+ assert_equal('#!/usr/bin/env ruby', zis.gets.chomp)
166
+ assert_equal("$VERBOSE =", zis.read(10))
167
+ end
168
+ end
169
+
170
+ end
@@ -0,0 +1,103 @@
1
+ require 'test_helper'
2
+ require 'zip/ioextras'
3
+
4
+ class AbstractInputStreamTest < MiniTest::Unit::TestCase
5
+ # AbstractInputStream subclass that provides a read method
6
+
7
+ TEST_LINES = ["Hello world#{$/}",
8
+ "this is the second line#{$/}",
9
+ "this is the last line"]
10
+ TEST_STRING = TEST_LINES.join
11
+ class TestAbstractInputStream
12
+ include ::Zip::IOExtras::AbstractInputStream
13
+
14
+ def initialize(aString)
15
+ super()
16
+ @contents = aString
17
+ @readPointer = 0
18
+ end
19
+
20
+ def sysread(charsToRead, buf = nil)
21
+ retVal=@contents[@readPointer, charsToRead]
22
+ @readPointer+=charsToRead
23
+ return retVal
24
+ end
25
+
26
+ def produce_input
27
+ sysread(100)
28
+ end
29
+
30
+ def input_finished?
31
+ @contents[@readPointer] == nil
32
+ end
33
+ end
34
+
35
+ def setup
36
+ @io = TestAbstractInputStream.new(TEST_STRING)
37
+ end
38
+
39
+ def test_gets
40
+ assert_equal(TEST_LINES[0], @io.gets)
41
+ assert_equal(1, @io.lineno)
42
+ assert_equal(TEST_LINES[0].length, @io.pos)
43
+ assert_equal(TEST_LINES[1], @io.gets)
44
+ assert_equal(2, @io.lineno)
45
+ assert_equal(TEST_LINES[2], @io.gets)
46
+ assert_equal(3, @io.lineno)
47
+ assert_equal(nil, @io.gets)
48
+ assert_equal(4, @io.lineno)
49
+ end
50
+
51
+ def test_getsMultiCharSeperator
52
+ assert_equal("Hell", @io.gets("ll"))
53
+ assert_equal("o world#{$/}this is the second l", @io.gets("d l"))
54
+ end
55
+
56
+ LONG_LINES = [
57
+ 'x'*48 + "\r\n",
58
+ 'y'*49 + "\r\n",
59
+ 'rest',
60
+ ]
61
+
62
+ def test_getsMulitCharSeperator_split
63
+ io = TestAbstractInputStream.new(LONG_LINES.join)
64
+ assert_equal(LONG_LINES[0], io.gets("\r\n"))
65
+ assert_equal(LONG_LINES[1], io.gets("\r\n"))
66
+ assert_equal(LONG_LINES[2], io.gets("\r\n"))
67
+ end
68
+
69
+ def test_getsWithSepAndIndex
70
+ io = TestAbstractInputStream.new(LONG_LINES.join)
71
+ assert_equal('x', io.gets("\r\n", 1))
72
+ assert_equal('x'*47 + "\r", io.gets("\r\n", 48))
73
+ assert_equal("\n", io.gets(nil, 1))
74
+ assert_equal('yy', io.gets(nil, 2))
75
+ end
76
+
77
+ def test_getsWithIndex
78
+ assert_equal(TEST_LINES[0], @io.gets(100))
79
+ assert_equal('this', @io.gets(4))
80
+ end
81
+
82
+ def test_each_line
83
+ lineNumber=0
84
+ @io.each_line {
85
+ |line|
86
+ assert_equal(TEST_LINES[lineNumber], line)
87
+ lineNumber+=1
88
+ }
89
+ end
90
+
91
+ def test_readlines
92
+ assert_equal(TEST_LINES, @io.readlines)
93
+ end
94
+
95
+ def test_readline
96
+ test_gets
97
+ begin
98
+ @io.readline
99
+ fail "EOFError expected"
100
+ rescue EOFError
101
+ end
102
+ end
103
+ end