diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..6f00c0c --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "timezone": "America/Toronto", + "branchNameStrict": true, + "commitBodyTable": true, + "configMigration": true, + "extends": [ + "config:recommended", + "group:allNonMajor", + "helpers:pinGitHubActionDigests", + "security:openssf-scorecard", + ":approveMajorUpdates", + ":automergeStableNonMajor", + ":automergePr", + ":combinePatchMinorReleases", + ":ignoreUnstable", + ":rebaseStalePrs", + ":prHourlyLimit2", + ":semanticCommits", + ":semanticPrefixChore", + ":separateMultipleMajorReleases", + ":timezone(America/Toronto)" + ], + "automergeSchedule": [ + "before 7am on Wednesday" + ], + "minimumReleaseAge": "3 days", + "platformAutomerge": true, + "schedule": [ + "before 7am on Tuesday" + ], + "customManagers": [ + { + "customType": "regex", + "datasourceTemplate": "rubygems", + "fileMatch": [ + "Rakefile" + ], + "matchStrings": [ + "extra_(?dev_)?deps << \\[\"(?[^\"]+)\"\\, \"(?[^\"]+)\"]" + ], + "depTypeTemplate": "{{#if isDev}}devDependencies{{else}}dependencies{{/if}}", + "versioningTemplate": "ruby" + } + ] +} \ No newline at end of file diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index d14bcb6..3fe389d 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -16,8 +16,6 @@ jobs: os: - ubuntu-20.04 ruby: - - '2.7' - - '3.0' - '3.1' - '3.2' - '3.3' @@ -38,10 +36,6 @@ jobs: - os: windows-latest ruby: jruby continue-on-error: true - - os: macos-latest - ruby: '2.7' - - os: macos-latest - ruby: '3.0' - os: macos-latest ruby: '3.1' - os: macos-latest @@ -73,5 +67,5 @@ jobs: - run: bundle exec ruby -S rake test --trace - - if: matrix.os == 'ubuntu-22.04' && matrix.ruby == '3.2' + - if: matrix.os == 'ubuntu-22.04' && matrix.ruby == '3.3' run: bundle exec standardrb diff --git a/.standard.yml b/.standard.yml index c325833..b665f63 100644 --- a/.standard.yml +++ b/.standard.yml @@ -1,8 +1,10 @@ --- parallel: true -ruby_version: 1.8.7 +ruby_version: 3.1 + ignore: - - 'archive-tar-minitar.gemspec' - 'minitar.gemspec' - - 'support/hoe/deprecated_gem.rb': - - Naming/ClassAndModuleCamelCase + +plugins: + - standard-minitest + - standard-thread_safety diff --git a/Gemfile b/Gemfile index 023ed3d..45284bf 100644 --- a/Gemfile +++ b/Gemfile @@ -3,4 +3,4 @@ source "https://rubygems.org/" # TODO: Remove this for minitar 1 since archive-tar-minitar is going away -gemspec :name => "minitar" +gemspec name: "minitar" diff --git a/History.md b/History.md index 426a108..d65eeb0 100644 --- a/History.md +++ b/History.md @@ -1,156 +1,169 @@ # History +## 1.0.0 / 2024-08-07 + +- Breaking Changes: + + - Minimum Ruby version is 3.1. + + - The `Archive::Tar::Minitar` namespace has been completely removed and + `Minitar` is a class instead of a module. + +- Enhancements: + + - Added `Minitar.pack_as_file`, originally proposed by John Prince back in + 2011 [#7][#7]. + ## 0.12 / 2024-08-06 -- Properly handle very long GNU filenames, resolving [#46][]. -- Handle very long GNU filenames that are 512 or more bytes, resolving [#45][]. - Originally implemented in [#47][] by Vijay, but accidentally closed. +- Properly handle very long GNU filenames, resolving [#46][#46]. +- Handle very long GNU filenames that are 512 or more bytes, resolving + [#45][#45]. Originally implemented in [#47][#47] by Vijay, but accidentally + closed. ## 0.11 / 2022-12-31 -- symlink support is complete. Merged as PR [#42][], rebased and built on top of - PR [#12][] by fetep. +- symlink support is complete. Merged as PR [#42][#42], rebased and built on top + of PR [#12][#12] by fetep. -- kymmt90 fixed a documentation error on Minitar.pack in PR [#43][]. +- kymmt90 fixed a documentation error on `Minitar.pack` in PR [#43][#43]. - This version is a soft-deprecation of all versions before Ruby 2.7, as they will no longer be tested in CI. ## 0.10 / 2022-03-26 -- nevesenin fixed an issue with long filename handling. Merged as PR [#40][]. +- nevesenin fixed an issue with long filename handling. Merged as PR [#40][#40]. ## 0.9 / 2019-09-04 -- jtappa added the ability to skip fsync with a new option to Minitar.unpack - and Minitar::Input#extract_entry. Provide `:fsync => false` as the last - parameter to enable. Merged from a modified version of PR [#37][]. +- jtappa added the ability to skip fsync with a new option to `Minitar.unpack` + and `Minitar::Input#extract_entry`. Provide `:fsync => false` as the last + parameter to enable. Merged from a modified version of PR [#37][#37]. ## 0.8 / 2019-01-05 -- inkstak resolved an issue introduced in the fix for [#31][] by allowing +- inkstak resolved an issue introduced in the fix for [#31][#31] by allowing spaces to be considered valid characters in strict octal handling. Octal - conversion ignores leading spaces. Merged from a slightly modified version - of PR [#35][]. + conversion ignores leading spaces. Merged from a slightly modified version of + PR [#35][#35]. -- dearblue contributed PR [#32][] providing an explicit call to #bytesize for +- dearblue contributed PR [#32][#32] providing an explicit call to #bytesize for strings that include multibyte characters. The PR has been modified to be compatible with older versions of Ruby and extend tests. -- Akinori MUSHA (knu) contributed PR [#36][] that treats certain badly - encoded regular files (with names ending in `/`) as if they were - directories on decode. +- Akinori MUSHA (knu) contributed PR [#36][#36] that treats certain badly + encoded regular files (with names ending in `/`) as if they were directories + on decode. ## 0.7 / 2018-02-19 -- Fixed issue [#28][] with a modified version of PR [#29][] covering the - security policy and position for Minitar. Thanks so much to ooooooo_q for +- Fixed issue [#28][#28] with a modified version of PR [#29][#29] covering the + security policy and position for `Minitar`. Thanks so much to ooooooo_q for the report and an initial patch. Additional information was added as - [#30][]. + [#30][#30]. -- dearblue contributed PR [#33][] providing a fix for Minitar::Reader when +- dearblue contributed PR [#33][#33] providing a fix for `Minitar::Reader` when the IO-like object does not have a `#pos` method. -- Kevin McDermott contributed PR [#34][] so that an InvalidTarStream is - raised if the tar header is not valid, preventing incorrect streaming of - files from a non-tarfile. This is a minor breaking change, so the version - has been bumped accordingly. +- Kevin McDermott contributed PR [#34][#34] so that an InvalidTarStream is + raised if the tar header is not valid, preventing incorrect streaming of files + from a non-tarfile. This is a minor breaking change, so the version has been + bumped accordingly. -- Kazuyoshi Kato contributed PR [#26][] providing support for the GNU tar +- Kazuyoshi Kato contributed PR [#26][#26] providing support for the GNU tar long filename extension. - Addressed a potential DOS with negative size fields in tar headers - ([#31][]). This has been handled in two ways: the size field in a tar - header is interpreted as a strict octal value and the Minitar reader will + ([#31][#31]). This has been handled in two ways: the size field in a tar + header is interpreted as a strict octal value and the `Minitar` reader will raise an InvalidTarStream if the size ends up being negative anyway. ## 0.6.1 / 2017-02-07 -- Fixed issue [#24][] where streams were being improperly closed immediately +- Fixed issue [#24][#24] where streams were being improperly closed immediately on open unless there was a block provided. -- Hopefully fixes issue [#23][] by releasing archive-tar-minitar after +- Hopefully fixes issue [#23][#23] by releasing archive-tar-minitar after minitar-cli is available. ## 0.6 / 2017-02-07 - Breaking Changes: - - Extracted `bin/minitar` into a new gem, `minitar-cli`. No, I am _not_ - going to bump the major version for this. As far as I can tell, few - people use the command-line utility anyway. (Installing - `archive-tar-minitar` will install both `minitar` and `minitar-cli`, at - least until version 1.0.) + - Extracted `bin/minitar` into a new gem, `minitar-cli`. No, I am _not_ going + to bump the major version for this. As far as I can tell, few people use the + command-line utility anyway. (Installing `archive-tar-minitar` will install + both `minitar` and `minitar-cli`, at least until version 1.0.) - - Minitar extraction before 0.6 traverses directories if the tarball - includes a relative directory reference, as reported in [#16][] by + - `Minitar` extraction before 0.6 traverses directories if the tarball + includes a relative directory reference, as reported in [#16][#16] by @ecneladis. This has been disallowed entirely and will throw a - SecureRelativePathError when found. Additionally, if the final - destination of an entry is an already-existing symbolic link, the - existing symbolic link will be removed and the file will be written - correctly (on platforms that support symblic links). + `SecureRelativePathError` when found. Additionally, if the final destination + of an entry is an already-existing symbolic link, the existing symbolic link + will be removed and the file will be written correctly (on platforms that + support symbolic links). - Enhancements: - - Licence change. After speaking with Mauricio Fernández, we have changed - the licensing of this library to Ruby and Simplified BSD and have - dropped the GNU GPL license. This takes effect from the 0.6 release. - - Printing a deprecation warning for including Archive::Tar to put - Minitar in the top-level namespace. - - Printing a deprecation warning for including Archive::Tar::Minitar into - a class (Minitar will be a class for version 1.0). - - Moved Archive::Tar::PosixHeader to Archive::Tar::Minitar::PosixHeader + - Licence change. After speaking with Mauricio Fernández, we have changed the + licensing of this library to Ruby and Simplified BSD and have dropped the + GNU GPL license. This takes effect from the 0.6 release. + - Printing a deprecation warning for including Archive::Tar to put `Minitar` + in the top-level namespace. + - Printing a deprecation warning for including `Archive::Tar::Minitar` into a + class (`Minitar` will be a class for version 1.0). + - Moved `Archive::Tar::PosixHeader` to `Archive::Tar::Minitar::PosixHeader` with a deprecation warning. Do not depend on - Archive::Tar::Minitar::PosixHeader, as it will be moving to - ::Minitar::PosixHeader in a future release. - - Added an alias, ::Minitar, for Archive::Tar::Minitar, opted in with + `Archive::Tar::Minitar::PosixHeader`, as it will be moving to + `::Minitar::PosixHeader` in a future release. + - Added an alias, `::Minitar`, for `Archive::Tar::Minitar`, opted in with `require 'minitar'`. In future releases, this alias will be enabled by - default, and the Archive::Tar namespace will be removed entirely for + default, and the `Archive::Tar` namespace will be removed entirely for version 1.0. - - Modified the handling of `mtime` in PosixHeader to do an integer - conversion (#to_i) so that a Time object can be used instead of the + - Modified the handling of `mtime` in `PosixHeader` to do an integer + conversion (`#to_i`) so that a Time object can be used instead of the integer value of the time object. - - Writer::RestrictedStream was renamed to Writer::WriteOnlyStream for - clarity. No alias or deprecation warning was provided for this as it is - an internal implementation detail. - - Writer::BoundedStream was renamed to Writer::BoundedWriteStream for + - `Writer::RestrictedStream` was renamed to `Writer::WriteOnlyStream` for + clarity. No alias or deprecation warning was provided for this as it is an + internal implementation detail. + - `Writer::BoundedStream` was renamed to `Writer::BoundedWriteStream` for clarity. A deprecation warning is provided on first use because a - BoundedWriteStream may raise a BoundedWriteStream::FileOverflow - exception. - - Writer::BoundedWriteStream::FileOverflow has been renamed to - Writer::WriteBoundaryOverflow and inherits from StandardError instead - of RuntimeError. Note that for Ruby 2.0 or higher, an error will be - raised when specifying Writer::BoundedWriteStream::FileOverflow because - Writer::BoundedWriteStream has been declared a private constant. - - Modified Writer#add_file_simple to accept the data for a - file in `opts[:data]`. When `opts[:data]` is provided, a stream block - must not be provided. Improved the documentation for this method. - - Modified Writer#add_file to accept `opts[:data]` and transparently call - Writer#add_file_simple in this case. + BoundedWriteStream may raise a `BoundedWriteStream::FileOverflow` exception. + - `Writer::BoundedWriteStream::FileOverflow` has been renamed to + `Writer::WriteBoundaryOverflow` and inherits from `StandardError` instead of + `RuntimeError`. Note that for Ruby 2.0 or higher, an error will be raised + when specifying `Writer::BoundedWriteStream::FileOverflow` because + `Writer::BoundedWriteStream` has been declared a private constant. + - Modified `Writer#add_file_simple` to accept the data for a file in + `opts[:data]`. When `opts[:data]` is provided, a stream block must not be + provided. Improved the documentation for this method. + - Modified `Writer#add_file` to accept `opts[:data]` and transparently call + `Writer#add_file_simple` in this case. - Methods that require blocks are no longer required, so the - Archive::Tar::Minitar::BlockRequired exception has been removed with a + `Archive::Tar::Minitar::BlockRequired` exception has been removed with a warning (this may not work on Ruby 1.8). - - Dramatically reduced the number of strings created when creating a - POSIX tarball header. - - Added a helper, Input.each_entry that iterates over each entry in an + - Dramatically reduced the number of strings created when creating a POSIX + tarball header. + - Added a helper, `Input.each_entry` that iterates over each entry in an opened entry object. - Bugs: - - Fix [#2][] to handle IO streams that are not seekable, such as pipes, - STDIN, or STDOUT. - - Fix [#3][] to make the test timezone resilient. - - Fix [#4][] for supporting the reading of tar files with filenames in - the GNU long filename extension format. Ported from @atoulme’s fork, - originally provided by Curtis Sampson. - - Fix [#6][] by making it raise the correct error for a long filename - with no path components. - - Fix [#13][] provided by @fetep fixes an off-by-one error on filename + - Fix [#2][#2] to handle IO streams that are not seekable, such as pipes, + `STDIN`, or `STDOUT`. + - Fix [#3][#3] to make the test timezone resilient. + - Fix [#4][#4] for supporting the reading of tar files with filenames in the + GNU long filename extension format. Ported from @atoulme’s fork, originally + provided by Curtis Sampson. + - Fix [#6][#6] by making it raise the correct error for a long filename with + no path components. + - Fix [#13][#13] provided by @fetep fixes an off-by-one error on filename splitting. - - Fix [#14][] provided by @kzys should fix Windows detection issues. - - Fix [#16][] as specified above. - - Fix an issue where Minitar.pack would not include Unix hidden files - when creating a tarball. + - Fix [#14][#14] provided by @kzys should fix Windows detection issues. + - Fix [#16][#16] as specified above. + - Fix an issue where `Minitar.pack` would not include Unix hidden files when + creating a tarball. - Development: @@ -169,13 +182,14 @@ ## 0.5.0 -- Initial release. Does files and directories. Command does create, extract, - and list. +- Initial release. Does files and directories. Command does create, extract, and + list. [#2]: https://github.com/halostatue/minitar/issues/2 [#3]: https://github.com/halostatue/minitar/issues/3 [#4]: https://github.com/halostatue/minitar/issues/4 [#6]: https://github.com/halostatue/minitar/issues/6 +[#7]: https://github.com/halostatue/minitar/issues/7 [#12]: https://github.com/halostatue/minitar/pull/12 [#13]: https://github.com/halostatue/minitar/issues/13 [#14]: https://github.com/halostatue/minitar/issues/14 diff --git a/Manifest.txt b/Manifest.txt index a0c3083..c4c4bfd 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -7,17 +7,16 @@ README.rdoc Rakefile docs/bsdl.txt docs/ruby.txt -lib/archive-tar-minitar.rb -lib/archive/tar/minitar.rb -lib/archive/tar/minitar/input.rb -lib/archive/tar/minitar/output.rb -lib/archive/tar/minitar/posix_header.rb -lib/archive/tar/minitar/reader.rb -lib/archive/tar/minitar/writer.rb lib/minitar.rb -support/hoe/deprecated_gem.rb +lib/minitar/input.rb +lib/minitar/output.rb +lib/minitar/posix_header.rb +lib/minitar/reader.rb +lib/minitar/writer.rb +renovate.json test/minitest_helper.rb test/support/tar_test_helpers.rb +test/test_issue_46.rb test/test_tar_header.rb test/test_tar_input.rb test/test_tar_output.rb diff --git a/Rakefile b/Rakefile index 99d62b2..925e972 100644 --- a/Rakefile +++ b/Rakefile @@ -11,7 +11,6 @@ Hoe.plugin :gemspec2 Hoe.plugin :git2 Hoe.plugin :minitest Hoe.plugin :rubygems -Hoe.plugin :deprecated_gem Hoe.plugin :cov Hoe.spec "minitar" do @@ -20,15 +19,10 @@ Hoe.spec "minitar" do self.history_file = "History.md" self.readme_file = "README.rdoc" - require_ruby_version ">= 1.8" + require_ruby_version ">= 3.1" self.licenses = ["Ruby", "BSD-2-Clause"] - self.post_install_message = <<-EOS -The `minitar` executable is no longer bundled with `minitar`. If you are -expecting this executable, make sure you also install `minitar-cli`. - EOS - spec_extras[:metadata] = ->(val) { val["rubygems_mfa_required"] = "true" } extra_dev_deps << ["base64", "~> 0.2"] @@ -43,4 +37,6 @@ expecting this executable, make sure you also install `minitar-cli`. extra_dev_deps << ["rake", ">= 10.0", "< 14"] extra_dev_deps << ["rdoc", ">= 0.0"] extra_dev_deps << ["standard", "~> 1.0"] + extra_dev_deps << ["standard-minitest", "~> 1.0"] + extra_dev_deps << ["standard-thread_safety", "~> 1.0"] end diff --git a/archive-tar-minitar.gemspec b/archive-tar-minitar.gemspec deleted file mode 100644 index f3f42b9..0000000 --- a/archive-tar-minitar.gemspec +++ /dev/null @@ -1,27 +0,0 @@ -# -*- encoding: utf-8 -*- -# stub: archive-tar-minitar 0.12 ruby lib - -Gem::Specification.new do |s| - s.name = "archive-tar-minitar".freeze - s.version = "0.12".freeze - - s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= - s.metadata = { "bug_tracker_uri" => "https://github.com/halostatue/minitar/issues", "homepage_uri" => "https://github.com/halostatue/minitar/", "rubygems_mfa_required" => "true", "source_code_uri" => "https://github.com/halostatue/minitar/" } if s.respond_to? :metadata= - s.require_paths = ["lib".freeze] - s.authors = ["Austin Ziegler".freeze] - s.date = "2024-08-06" - s.description = "'archive-tar-minitar' has been deprecated; just install 'minitar'. The minitar library is a pure-Ruby library that provides the ability to deal\nwith POSIX tar(1) archive files.\n\nThis is release 0.12. This is likely the last revision before 1.0.\n\nminitar (previously called Archive::Tar::Minitar) is based heavily on code\noriginally written by Mauricio Julio Fern\u00E1ndez Pradier for the rpa-base\nproject.".freeze - s.email = ["halostatue@gmail.com".freeze] - s.files = ["lib/archive-tar-minitar.rb".freeze] - s.homepage = "https://github.com/halostatue/minitar/".freeze - s.licenses = ["Ruby".freeze, "BSD-2-Clause".freeze] - s.post_install_message = "'archive-tar-minitar' has been deprecated; just install 'minitar'.".freeze - s.required_ruby_version = Gem::Requirement.new(">= 1.8".freeze) - s.rubygems_version = "3.5.17".freeze - s.summary = "'archive-tar-minitar' has been deprecated; just install 'minitar'.".freeze - - s.specification_version = 4 - - s.add_runtime_dependency(%q.freeze, ["~> 0.12".freeze]) - s.add_runtime_dependency(%q.freeze, ["~> 0.12".freeze]) -end diff --git a/lib/archive-tar-minitar.rb b/lib/archive-tar-minitar.rb deleted file mode 100644 index 6ea84a3..0000000 --- a/lib/archive-tar-minitar.rb +++ /dev/null @@ -1,3 +0,0 @@ -# coding: utf-8 - -require "archive/tar/minitar" diff --git a/lib/archive/tar/minitar.rb b/lib/archive/tar/minitar.rb deleted file mode 100644 index 9fa81fa..0000000 --- a/lib/archive/tar/minitar.rb +++ /dev/null @@ -1,311 +0,0 @@ -# coding: utf-8 - -## -module Archive; end - -## -module Archive::Tar; end - -require "fileutils" -require "rbconfig" - -class << Archive::Tar # :nodoc: - def const_missing(const) # :nodoc: - case const - when :PosixHeader - warn "Archive::Tar::PosixHeader has been renamed to " \ - "Archive::Tar::Minitar::PosixHeader" - const_set :PosixHeader, Archive::Tar::Minitar::PosixHeader - else - super - end - end - - private - - def included(mod) - return if modules.include?(mod) - warn "Including Minitar via the #{self} namespace is deprecated." - modules.add mod - end - - def modules - require "set" - @modules ||= Set.new - end -end - -# == Synopsis -# -# Using minitar is easy. The simplest case is: -# -# require 'zlib' -# require 'minitar' -# -# # Packs everything that matches Find.find('tests'). -# # test.tar will automatically be closed by Minitar.pack. -# Minitar.pack('tests', File.open('test.tar', 'wb')) -# -# # Unpacks 'test.tar' to 'x', creating 'x' if necessary. -# Minitar.unpack('test.tar', 'x') -# -# A gzipped tar can be written with: -# -# # test.tgz will be closed automatically. -# Minitar.pack('tests', Zlib::GzipWriter.new(File.open('test.tgz', 'wb')) -# -# # test.tgz will be closed automatically. -# Minitar.unpack(Zlib::GzipReader.new(File.open('test.tgz', 'rb')), 'x') -# -# As the case above shows, one need not write to a file. However, it will -# sometimes require that one dive a little deeper into the API, as in the case -# of StringIO objects. Note that I'm not providing a block with -# Minitar::Output, as Minitar::Output#close automatically closes both the -# Output object and the wrapped data stream object. -# -# begin -# sgz = Zlib::GzipWriter.new(StringIO.new("")) -# tar = Output.new(sgz) -# Find.find('tests') do |entry| -# Minitar.pack_file(entry, tar) -# end -# ensure -# # Closes both tar and sgz. -# tar.close -# end -module Archive::Tar::Minitar - VERSION = "0.12".freeze # :nodoc: - - # The base class for any minitar error. - Error = Class.new(::StandardError) - # Raised when a wrapped data stream class is not seekable. - NonSeekableStream = Class.new(Error) - # The exception raised when operations are performed on a stream that has - # previously been closed. - ClosedStream = Class.new(Error) - # The exception raised when a filename exceeds 256 bytes in length, the - # maximum supported by the standard Tar format. - FileNameTooLong = Class.new(Error) - # The exception raised when a data stream ends before the amount of data - # expected in the archive's PosixHeader. - UnexpectedEOF = Class.new(StandardError) - # The exception raised when a file contains a relative path in secure mode - # (the default for this version). - SecureRelativePathError = Class.new(Error) - # The exception raised when a file contains an invalid Posix header. - InvalidTarStream = Class.new(Error) - - class << self - # Tests if +path+ refers to a directory. Fixes an apparently - # corrupted stat() call on Windows. - def dir?(path) - # rubocop:disable Style/CharacterLiteral - File.directory?((path[-1] == ?/) ? path : "#{path}/") - # rubocop:enable Style/CharacterLiteral - end - - # A convenience method for wrapping Archive::Tar::Minitar::Input.open - # (mode +r+) and Archive::Tar::Minitar::Output.open (mode +w+). No other - # modes are currently supported. - def open(dest, mode = "r", &block) - case mode - when "r" - Input.open(dest, &block) - when "w" - Output.open(dest, &block) - else - raise "Unknown open mode for Archive::Tar::Minitar.open." - end - end - - def const_missing(c) # :nodoc: - case c - when :BlockRequired - warn "This constant has been removed." - const_set(:BlockRequired, Class.new(StandardError)) - else - super - end - end - - def windows? # :nodoc: - RbConfig::CONFIG["host_os"] =~ /^(mswin|mingw|cygwin)/ - end - - # A convenience method to pack the file provided. +entry+ may either be a - # filename (in which case various values for the file (see below) will be - # obtained from File#stat(entry) or a Hash with the fields: - # - # :name:: The filename to be packed into the archive. Required. - # :mode:: The mode to be applied. - # :uid:: The user owner of the file. (Ignored on Windows.) - # :gid:: The group owner of the file. (Ignored on Windows.) - # :mtime:: The modification Time of the file. - # - # During packing, if a block is provided, #pack_file yields an +action+ - # Symol, the full name of the file being packed, and a Hash of - # statistical information, just as with - # Archive::Tar::Minitar::Input#extract_entry. - # - # The +action+ will be one of: - # :dir:: The +entry+ is a directory. - # :file_start:: The +entry+ is a file; the extract of the - # file is just beginning. - # :file_progress:: Yielded every 4096 bytes during the extract - # of the +entry+. - # :file_done:: Yielded when the +entry+ is completed. - # - # The +stats+ hash contains the following keys: - # :current:: The current total number of bytes read in the - # +entry+. - # :currinc:: The current number of bytes read in this read - # cycle. - # :name:: The filename to be packed into the tarchive. - # *REQUIRED*. - # :mode:: The mode to be applied. - # :uid:: The user owner of the file. (+nil+ on Windows.) - # :gid:: The group owner of the file. (+nil+ on Windows.) - # :mtime:: The modification Time of the file. - def pack_file(entry, outputter) # :yields action, name, stats: - if outputter.is_a?(Archive::Tar::Minitar::Output) - outputter = outputter.tar - end - - stats = {} - - if entry.is_a?(Hash) - name = entry[:name] - entry.each { |kk, vv| stats[kk] = vv unless vv.nil? } - else - name = entry - end - - name = name.sub(%r{\./}, "") - stat = File.stat(name) - stats[:mode] ||= stat.mode - stats[:mtime] ||= stat.mtime - stats[:size] = stat.size - - if windows? - stats[:uid] = nil - stats[:gid] = nil - else - stats[:uid] ||= stat.uid - stats[:gid] ||= stat.gid - end - - if File.file?(name) - outputter.add_file_simple(name, stats) do |os| - stats[:current] = 0 - yield :file_start, name, stats if block_given? - File.open(name, "rb") do |ff| - until ff.eof? - stats[:currinc] = os.write(ff.read(4096)) - stats[:current] += stats[:currinc] - yield :file_progress, name, stats if block_given? - end - end - yield :file_done, name, stats if block_given? - end - elsif dir?(name) - yield :dir, name, stats if block_given? - outputter.mkdir(name, stats) - else - raise "Don't yet know how to pack this type of file." - end - end - - # A convenience method to pack files specified by +src+ into +dest+. If - # +src+ is an Array, then each file detailed therein will be packed into - # the resulting Archive::Tar::Minitar::Output stream; if +recurse_dirs+ is - # true, then directories will be recursed. - # - # If +src+ is not an Array, it will be treated as the result of Find.find; - # all files matching will be packed. - def pack(src, dest, recurse_dirs = true, &block) - require "find" - Output.open(dest) do |outp| - if src.is_a?(Array) - src.each do |entry| - if dir?(entry) && recurse_dirs - Find.find(entry) do |ee| - pack_file(ee, outp, &block) - end - else - pack_file(entry, outp, &block) - end - end - else - Find.find(src) do |entry| - pack_file(entry, outp, &block) - end - end - end - end - - # A convenience method to unpack files from +src+ into the directory - # specified by +dest+. Only those files named explicitly in +files+ - # will be extracted. - def unpack(src, dest, files = [], options = {}, &block) - Input.open(src) do |inp| - if File.exist?(dest) && !dir?(dest) - raise "Can't unpack to a non-directory." - end - - FileUtils.mkdir_p(dest) unless File.exist?(dest) - - inp.each do |entry| - if files.empty? || files.include?(entry.full_name) - inp.extract_entry(dest, entry, options, &block) - end - end - end - end - - # Check whether +io+ can seek without errors. - def seekable?(io, methods = nil) - # The IO class throws an exception at runtime if we try to change - # position on a non-regular file. - if io.respond_to?(:stat) - io.stat.file? - else - # Duck-type the rest of this. - methods ||= [:pos, :pos=, :seek, :rewind] - methods = [methods] unless methods.is_a?(Array) - methods.all? { |m| io.respond_to?(m) } - end - end - - private - - def included(mod) - return if modules.include?(mod) - warn "Including #{self} has been deprecated (Minitar will become a class)." - modules << mod - end - - def modules - require "set" - @modules ||= Set.new - end - end - - # This exists to make bytesize implementations work across Ruby versions. - module ByteSize # :nodoc: - private - - if "".respond_to?(:bytesize) - def bytesize(item) - item.bytesize - end - else - def bytesize(item) - item.size - end - end - end -end - -require "archive/tar/minitar/posix_header" -require "archive/tar/minitar/input" -require "archive/tar/minitar/output" diff --git a/lib/archive/tar/minitar/posix_header.rb b/lib/archive/tar/minitar/posix_header.rb deleted file mode 100644 index 3106a62..0000000 --- a/lib/archive/tar/minitar/posix_header.rb +++ /dev/null @@ -1,278 +0,0 @@ -# coding: utf-8 - -## -module Archive; end - -## -module Archive::Tar; end - -## -module Archive::Tar::Minitar; end - -# Implements the POSIX tar header as a Ruby class. The structure of -# the POSIX tar header is: -# -# struct tarfile_entry_posix -# { // pack/unpack -# char name[100]; // ASCII (+ Z unless filled) a100/Z100 -# char mode[8]; // 0 padded, octal, null a8 /A8 -# char uid[8]; // 0 padded, octal, null a8 /A8 -# char gid[8]; // 0 padded, octal, null a8 /A8 -# char size[12]; // 0 padded, octal, null a12 /A12 -# char mtime[12]; // 0 padded, octal, null a12 /A12 -# char checksum[8]; // 0 padded, octal, null, space a8 /A8 -# char typeflag[1]; // see below a /a -# char linkname[100]; // ASCII + (Z unless filled) a100/Z100 -# char magic[6]; // "ustar\0" a6 /A6 -# char version[2]; // "00" a2 /A2 -# char uname[32]; // ASCIIZ a32 /Z32 -# char gname[32]; // ASCIIZ a32 /Z32 -# char devmajor[8]; // 0 padded, octal, null a8 /A8 -# char devminor[8]; // 0 padded, octal, null a8 /A8 -# char prefix[155]; // ASCII (+ Z unless filled) a155/Z155 -# }; -# -# The #typeflag is one of several known values. -# -# POSIX indicates that "A POSIX-compliant implementation must treat any -# unrecognized typeflag value as a regular file." -class Archive::Tar::Minitar::PosixHeader - BLOCK_SIZE = 512 - MAGIC_BYTES = "ustar".freeze - - GNU_EXT_LONG_LINK = "././@LongLink" - - # Fields that must be set in a POSIX tar(1) header. - REQUIRED_FIELDS = [:name, :size, :prefix, :mode].freeze - # Fields that may be set in a POSIX tar(1) header. - OPTIONAL_FIELDS = [ - :uid, :gid, :mtime, :checksum, :typeflag, :linkname, :magic, :version, - :uname, :gname, :devmajor, :devminor - ].freeze - - # All fields available in a POSIX tar(1) header. - FIELDS = (REQUIRED_FIELDS + OPTIONAL_FIELDS).freeze - - FIELDS.each do |f| - attr_reader f.to_sym unless f.to_sym == :name - end - - # The name of the file. By default, limited to 100 bytes. Required. May be - # longer (up to BLOCK_SIZE bytes) if using the GNU long name tar extension. - attr_accessor :name - - # The pack format passed to Array#pack for encoding a header. - HEADER_PACK_FORMAT = "a100a8a8a8a12a12a7aaa100a6a2a32a32a8a8a155".freeze - # The unpack format passed to String#unpack for decoding a header. - HEADER_UNPACK_FORMAT = "Z100A8A8A8A12A12A8aZ100A6A2Z32Z32A8A8Z155".freeze - - class << self - # Creates a new PosixHeader from a data stream. - def from_stream(stream) - from_data(stream.read(BLOCK_SIZE)) - end - - # Creates a new PosixHeader from a data stream. Deprecated; use - # PosixHeader.from_stream instead. - def new_from_stream(stream) - warn "#{__method__} has been deprecated; use from_stream instead." - from_stream(stream) - end - - # Creates a new PosixHeader from a BLOCK_SIZE-byte data buffer. - def from_data(data) - fields = data.unpack(HEADER_UNPACK_FORMAT) - name = fields.shift - mode = fields.shift.oct - uid = fields.shift.oct - gid = fields.shift.oct - size = strict_oct(fields.shift) - mtime = fields.shift.oct - checksum = fields.shift.oct - typeflag = fields.shift - linkname = fields.shift - magic = fields.shift - version = fields.shift.oct - uname = fields.shift - gname = fields.shift - devmajor = fields.shift.oct - devminor = fields.shift.oct - prefix = fields.shift - - empty = !data.each_byte.any?(&:nonzero?) - - new( - :name => name, - :mode => mode, - :uid => uid, - :gid => gid, - :size => size, - :mtime => mtime, - :checksum => checksum, - :typeflag => typeflag, - :magic => magic, - :version => version, - :uname => uname, - :gname => gname, - :devmajor => devmajor, - :devminor => devminor, - :prefix => prefix, - :empty => empty, - :linkname => linkname - ) - end - - private - - def strict_oct(string) - return string.oct if /\A[0-7 ]*\z/.match?(string) - raise ArgumentError, "#{string.inspect} is not a valid octal string" - end - end - - # Creates a new PosixHeader. A PosixHeader cannot be created unless - # +name+, +size+, +prefix+, and +mode+ are provided. - def initialize(v) - REQUIRED_FIELDS.each do |f| - raise ArgumentError, "Field #{f} is required." unless v.key?(f) - end - - v[:mtime] = v[:mtime].to_i - v[:checksum] ||= "" - v[:typeflag] ||= "0" - v[:magic] ||= MAGIC_BYTES - v[:version] ||= "00" - - FIELDS.each do |f| - instance_variable_set(:"@#{f}", v[f]) - end - - @empty = v[:empty] - end - - # Indicates if the header was an empty header. - def empty? - @empty - end - - # Indicates if the header has a valid magic value. - def valid? - empty? || @magic == MAGIC_BYTES - end - - # Returns +true+ if the header is a long name special header which indicates - # that the next block of data is the filename. - def long_name? - typeflag == "L" && name == GNU_EXT_LONG_LINK - end - - # A string representation of the header. - def to_s - update_checksum - header(@checksum) - end - alias_method :to_str, :to_s - - # Update the checksum field. - def update_checksum - hh = header(" " * 8) - @checksum = oct(calculate_checksum(hh), 6) - end - - private - - include Archive::Tar::Minitar::ByteSize - - def oct(num, len) - if num.nil? - "\0" * (len + 1) - else - "%0#{len}o" % num - end - end - - def calculate_checksum(hdr) - hdr.unpack("C*").inject { |a, e| a + e } - end - - def header(chksum) - arr = [name, oct(mode, 7), oct(uid, 7), oct(gid, 7), oct(size, 11), - oct(mtime, 11), chksum, " ", typeflag, linkname, magic, version, - uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix] - str = arr.pack(HEADER_PACK_FORMAT) - str + "\0" * ((BLOCK_SIZE - bytesize(str)) % BLOCK_SIZE) - end - - ## - # :attr_reader: size - # The size of the file. Required. - - ## - # :attr_reader: prefix - # The prefix of the file; the path before #name. Limited to 155 bytes. - # Required. - - ## - # :attr_reader: mode - # The Unix file mode of the file. Stored as an octal integer. Required. - - ## - # :attr_reader: uid - # The Unix owner user ID of the file. Stored as an octal integer. - - ## - # :attr_reader: uname - # The user name of the Unix owner of the file. - - ## - # :attr_reader: gid - # The Unix owner group ID of the file. Stored as an octal integer. - - ## - # :attr_reader: gname - # The group name of the Unix owner of the file. - - ## - # :attr_reader: mtime - # The modification time of the file in epoch seconds. Stored as an - # octal integer. - - ## - # :attr_reader: checksum - # The checksum of the file. Stored as an octal integer. Calculated - # before encoding the header as a string. - - ## - # :attr_reader: typeflag - # The type of record in the file. - # - # +0+:: Regular file. NULL should be treated as a synonym, for compatibility - # purposes. - # +1+:: Hard link. - # +2+:: Symbolic link. - # +3+:: Character device node. - # +4+:: Block device node. - # +5+:: Directory. - # +6+:: FIFO node. - # +7+:: Reserved. - - ## - # :attr_reader: linkname - # The name of the link stored. Not currently used. - - ## - # :attr_reader: magic - # Always "ustar\0". - - ## - # :attr_reader: version - # Always "00" - - ## - # :attr_reader: devmajor - # The major device ID. Not currently used. - - ## - # :attr_reader: devminor - # The minor device ID. Not currently used. -end diff --git a/lib/minitar.rb b/lib/minitar.rb index bc4e7d0..7ab1a57 100644 --- a/lib/minitar.rb +++ b/lib/minitar.rb @@ -1,12 +1,300 @@ -# coding: utf-8 +require "fileutils" +require "rbconfig" -require "archive/tar/minitar" +# == Synopsis +# +# Using minitar is easy. The simplest case is: +# +# require 'zlib' +# require 'minitar' +# +# # Packs everything that matches Find.find('tests'). +# # test.tar will automatically be closed by Minitar.pack. +# Minitar.pack('tests', File.open('test.tar', 'wb')) +# +# # Unpacks 'test.tar' to 'x', creating 'x' if necessary. +# Minitar.unpack('test.tar', 'x') +# +# A gzipped tar can be written with: +# +# # test.tgz will be closed automatically. +# Minitar.pack('tests', Zlib::GzipWriter.new(File.open('test.tgz', 'wb')) +# +# # test.tgz will be closed automatically. +# Minitar.unpack(Zlib::GzipReader.new(File.open('test.tgz', 'rb')), 'x') +# +# As the case above shows, one need not write to a file. However, it will +# sometimes require that one dive a little deeper into the API, as in the case +# of StringIO objects. Note that I'm not providing a block with +# Minitar::Output, as Minitar::Output#close automatically closes both the +# Output object and the wrapped data stream object. +# +# begin +# sgz = Zlib::GzipWriter.new(StringIO.new("")) +# tar = Output.new(sgz) +# Find.find('tests') do |entry| +# Minitar.pack_file(entry, tar) +# end +# ensure +# # Closes both tar and sgz. +# tar.close +# end +class Minitar + VERSION = "1.0.0".freeze # :nodoc: -if defined?(::Minitar) && ::Minitar != Archive::Tar::Minitar - warn <<-EOS -::Minitar is already defined. -This will conflict with future versions of minitar. - EOS -else - ::Minitar = Archive::Tar::Minitar + # The base class for any minitar error. + Error = Class.new(::StandardError) + # Raised when a wrapped data stream class is not seekable. + NonSeekableStream = Class.new(Error) + # The exception raised when operations are performed on a stream that has + # previously been closed. + ClosedStream = Class.new(Error) + # The exception raised when a filename exceeds 256 bytes in length, the + # maximum supported by the standard Tar format. + FileNameTooLong = Class.new(Error) + # The exception raised when a data stream ends before the amount of data + # expected in the archive's PosixHeader. + UnexpectedEOF = Class.new(StandardError) + # The exception raised when a file contains a relative path in secure mode + # (the default for this version). + SecureRelativePathError = Class.new(Error) + # The exception raised when a file contains an invalid Posix header. + InvalidTarStream = Class.new(Error) end + +class << Minitar + # Tests if +path+ refers to a directory. Fixes an apparently + # corrupted stat() call on Windows. + def dir?(path) + File.directory?((path[-1] == "/") ? path : "#{path}/") + end + + # A convenience method for wrapping Minitar::Input.open + # (mode +r+) and Minitar::Output.open (mode +w+). No other + # modes are currently supported. + def open(dest, mode = "r", &) + case mode + when "r" + Input.open(dest, &) + when "w" + Output.open(dest, &block) + else + raise "Unknown open mode for Minitar.open." + end + end + + def const_missing(c) # :nodoc: + case c + when :BlockRequired + warn "This constant has been removed." + const_set(:BlockRequired, Class.new(StandardError)) + else + super + end + end + + def windows? # :nodoc: + RbConfig::CONFIG["host_os"] =~ /^(mswin|mingw|cygwin)/ + end + + # A convenience method to pack the provided +data+ as a file named +entry+. +entry+ may + # either be a name or a Hash with the fields described below. When only a name is + # provided, or only some Hash fields are provided, the default values will apply. + # + # :name:: The filename to be packed into the archive. Required. + # :mode:: The mode to be applied. Defaults to 0o644 for files and 0o755 for + # directories. + # :uid:: The user owner of the file. Default is +nil+. + # :gid:: The group owner of the file. Default is +nil+. + # :mtime:: The modification Time of the file. Default is +Time.now+. + # + # If +data+ is +nil+, a directory will be created. Use an empty String for a normal + # empty file. + def pack_as_file(entry, data, outputter) # :yields action, name, stats: + if outputter.is_a?(Minitar::Output) + outputter = outputter.tar + end + + stats = { + gid: nil, + uid: nil, + mtime: Time.now, + size: data&.size || 0, + mode: data ? 0o644 : 0o755 + } + + if entry.is_a?(Hash) + name = entry.delete(:name) + entry.each_pair { stats[_1] = _2 unless _2.nil? } + else + name = entry + end + + if data.nil? # Create a directory + yield :dir, name, stats if block_given? + outputter.mkdir(name, stats) + else + outputter.add_file_simple(name, stats) do |os| + stats[:current] = 0 + yield :file_start, name, stats if block_given? + + StringIO.open(data, "rb") do |ff| + until ff.eof? + stats[:currinc] = os.write(ff.read(4096)) + stats[:current] += stats[:currinc] + yield :file_progress, name, stats if block_given? + end + end + + yield :file_done, name, stats if block_given? + end + end + end + + # A convenience method to pack the file provided. +entry+ may either be a filename (in + # which case various values for the file (see below) will be obtained from + # File#stat(entry) or a Hash with the fields: + # + # :name:: The filename to be packed into the archive. Required. + # :mode:: The mode to be applied. + # :uid:: The user owner of the file. (Ignored on Windows.) + # :gid:: The group owner of the file. (Ignored on Windows.) + # :mtime:: The modification Time of the file. + # + # During packing, if a block is provided, #pack_file yields an +action+ Symol, the + # full name of the file being packed, and a Hash of statistical information, just as + # with Minitar::Input#extract_entry. + # + # The +action+ will be one of: + # :dir:: The +entry+ is a directory. + # :file_start:: The +entry+ is a file; the extract of the + # file is just beginning. + # :file_progress:: Yielded every 4096 bytes during the extract + # of the +entry+. + # :file_done:: Yielded when the +entry+ is completed. + # + # The +stats+ hash contains the following keys: + # :current:: The current total number of bytes read in the + # +entry+. + # :currinc:: The current number of bytes read in this read + # cycle. + # :name:: The filename to be packed into the tarchive. + # *REQUIRED*. + # :mode:: The mode to be applied. + # :uid:: The user owner of the file. (+nil+ on Windows.) + # :gid:: The group owner of the file. (+nil+ on Windows.) + # :mtime:: The modification Time of the file. + def pack_file(entry, outputter) # :yields action, name, stats: + if outputter.is_a?(Minitar::Output) + outputter = outputter.tar + end + + stats = {} + + if entry.is_a?(Hash) + name = entry[:name] + entry.each { |kk, vv| stats[kk] = vv unless vv.nil? } + else + name = entry + end + + name = name.sub(%r{\./}, "") + stat = File.stat(name) + stats[:mode] ||= stat.mode + stats[:mtime] ||= stat.mtime + stats[:size] = stat.size + + if windows? + stats[:uid] = nil + stats[:gid] = nil + else + stats[:uid] ||= stat.uid + stats[:gid] ||= stat.gid + end + + if File.file?(name) + outputter.add_file_simple(name, stats) do |os| + stats[:current] = 0 + yield :file_start, name, stats if block_given? + File.open(name, "rb") do |ff| + until ff.eof? + stats[:currinc] = os.write(ff.read(4096)) + stats[:current] += stats[:currinc] + yield :file_progress, name, stats if block_given? + end + end + yield :file_done, name, stats if block_given? + end + elsif dir?(name) + yield :dir, name, stats if block_given? + outputter.mkdir(name, stats) + else + raise "Don't yet know how to pack this type of file." + end + end + + # A convenience method to pack files specified by +src+ into +dest+. If + # +src+ is an Array, then each file detailed therein will be packed into + # the resulting Minitar::Output stream; if +recurse_dirs+ is + # true, then directories will be recursed. + # + # If +src+ is not an Array, it will be treated as the result of Find.find; + # all files matching will be packed. + def pack(src, dest, recurse_dirs = true, &block) + require "find" + Minitar::Output.open(dest) do |outp| + if src.is_a?(Array) + src.each do |entry| + if dir?(entry) && recurse_dirs + Find.find(entry) do |ee| + pack_file(ee, outp, &block) + end + else + pack_file(entry, outp, &block) + end + end + else + Find.find(src) do |entry| + pack_file(entry, outp, &block) + end + end + end + end + + # A convenience method to unpack files from +src+ into the directory + # specified by +dest+. Only those files named explicitly in +files+ + # will be extracted. + def unpack(src, dest, files = [], options = {}, &block) + Minitar::Input.open(src) do |inp| + if File.exist?(dest) && !dir?(dest) + raise "Can't unpack to a non-directory." + end + + FileUtils.mkdir_p(dest) unless File.exist?(dest) + + inp.each do |entry| + if files.empty? || files.include?(entry.full_name) + inp.extract_entry(dest, entry, options, &block) + end + end + end + end + + # Check whether +io+ can seek without errors. + def seekable?(io, methods = nil) + # The IO class throws an exception at runtime if we try to change + # position on a non-regular file. + if io.respond_to?(:stat) + io.stat.file? + else + # Duck-type the rest of this. + methods ||= [:pos, :pos=, :seek, :rewind] + methods = [methods] unless methods.is_a?(Array) + methods.all? { |m| io.respond_to?(m) } + end + end +end + +require "minitar/posix_header" +require "minitar/input" +require "minitar/output" diff --git a/lib/archive/tar/minitar/input.rb b/lib/minitar/input.rb similarity index 83% rename from lib/archive/tar/minitar/input.rb rename to lib/minitar/input.rb index 6565c01..0fb15ce 100644 --- a/lib/archive/tar/minitar/input.rb +++ b/lib/minitar/input.rb @@ -1,9 +1,7 @@ -# coding: utf-8 +require "minitar/reader" -require "archive/tar/minitar/reader" - -module Archive::Tar::Minitar - # Wraps a Archive::Tar::Minitar::Reader with convenience methods and wrapped +class Minitar + # Wraps a Minitar::Reader with convenience methods and wrapped # stream management; Input only works with data streams that can be rewound. class Input include Enumerable @@ -15,8 +13,8 @@ class Input # instance, +Input.open+ returns the value of the block. # # call-seq: - # Archive::Tar::Minitar::Input.open(io) -> input - # Archive::Tar::Minitar::Input.open(io) { |input| block } -> obj + # Minitar::Input.open(io) -> input + # Minitar::Input.open(io) { |input| block } -> obj def self.open(input) stream = new(input) @@ -36,7 +34,7 @@ def self.open(input) # Iterates over each entry in the provided input. This wraps the common # pattern of: # - # Archive::Tar::Minitar::Input.open(io) do |i| + # Minitar::Input.open(io) do |i| # inp.each do |entry| # # ... # end @@ -46,8 +44,8 @@ def self.open(input) # behaviour. # # call-seq: - # Archive::Tar::Minitar::Input.each_entry(io) -> enumerator - # Archive::Tar::Minitar::Input.each_entry(io) { |entry| block } -> obj + # Minitar::Input.each_entry(io) -> enumerator + # Minitar::Input.each_entry(io) { |entry| block } -> obj def self.each_entry(input) return to_enum(__method__, input) unless block_given? @@ -67,8 +65,8 @@ def self.each_entry(input) # support rewinding. # # call-seq: - # Archive::Tar::Minitar::Input.new(io) -> input - # Archive::Tar::Minitar::Input.new(path) -> input + # Minitar::Input.new(io) -> input + # Minitar::Input.new(path) -> input def initialize(input) @io = if input.respond_to?(:read) input @@ -76,8 +74,8 @@ def initialize(input) ::Kernel.open(input, "rb") end - unless Archive::Tar::Minitar.seekable?(@io, :rewind) - raise Archive::Tar::Minitar::NonSeekableStream + unless Minitar.seekable?(@io, :rewind) + raise Minitar::NonSeekableStream end @tar = Reader.new(@io) @@ -117,11 +115,11 @@ def each_entry # cycle. # :entry:: The entry being extracted; this is a # Reader::EntryStream, with all methods thereof. - def extract_entry(destdir, entry, options = {}, &block) # :yields action, name, stats: + def extract_entry(destdir, entry, options = {}, &) # :yields action, name, stats: stats = { - :current => 0, - :currinc => 0, - :entry => entry + current: 0, + currinc: 0, + entry: entry } # extract_entry is not vulnerable to prefix '/' vulnerabilities, but it @@ -140,9 +138,9 @@ def extract_entry(destdir, entry, options = {}, &block) # :yields action, name, end if entry.directory? - extract_directory(destdir, full_name, entry, stats, options, &block) + extract_directory(destdir, full_name, entry, stats, options, &) else # it's a file - extract_file(destdir, full_name, entry, stats, options, &block) + extract_file(destdir, full_name, entry, stats, options, &) end end @@ -169,7 +167,7 @@ def fsync_dir(dirname) rescue # ignore IOError if it's an unpatched (old) Ruby nil ensure - dir.close if dir rescue nil # rubocop:disable Style/RescueModifier + dir&.close rescue nil # standard:disable Style/RescueModifier end def extract_directory(destdir, full_name, entry, stats, options) @@ -177,7 +175,7 @@ def extract_directory(destdir, full_name, entry, stats, options) yield :dir, full_name, stats if block_given? - if Archive::Tar::Minitar.dir?(dest) + if Minitar.dir?(dest) begin FileUtils.chmod(entry.mode, dest) rescue @@ -186,7 +184,7 @@ def extract_directory(destdir, full_name, entry, stats, options) else File.unlink(dest.chomp("/")) if File.symlink?(dest.chomp("/")) - FileUtils.mkdir_p(dest, :mode => entry.mode) + FileUtils.mkdir_p(dest, mode: entry.mode) FileUtils.chmod(entry.mode, dest) end @@ -198,16 +196,14 @@ def extract_directory(destdir, full_name, entry, stats, options) def extract_file(destdir, full_name, entry, stats, options) destdir = File.join(destdir, File.dirname(full_name)) - FileUtils.mkdir_p(destdir, :mode => 0o755) + FileUtils.mkdir_p(destdir, mode: 0o755) destfile = File.join(destdir, File.basename(full_name)) File.unlink(destfile) if File.symlink?(destfile) # Errno::ENOENT - # rubocop:disable Style/RescueModifier - FileUtils.chmod(0o600, destfile) rescue nil - # rubocop:enable Style/RescueModifier + FileUtils.chmod(0o600, destfile) rescue nil # standard:disable Style/RescueModifier yield :file_start, full_name, stats if block_given? diff --git a/lib/archive/tar/minitar/output.rb b/lib/minitar/output.rb similarity index 77% rename from lib/archive/tar/minitar/output.rb rename to lib/minitar/output.rb index 2dbe9e2..7f8eff1 100644 --- a/lib/archive/tar/minitar/output.rb +++ b/lib/minitar/output.rb @@ -1,9 +1,7 @@ -# coding: utf-8 +require "minitar/writer" -require "archive/tar/minitar/writer" - -module Archive::Tar::Minitar - # Wraps a Archive::Tar::Minitar::Writer with convenience methods and wrapped +class Minitar + # Wraps a Minitar::Writer with convenience methods and wrapped # stream management. If the stream provided to Output does not support random # access, only Writer#add_file_simple and Writer#mkdir are guaranteed to # work. @@ -15,8 +13,8 @@ class Output # instance, +Output.open+ returns the value of the block. # # call-seq: - # Archive::Tar::Minitar::Output.open(io) -> output - # Archive::Tar::Minitar::Output.open(io) { |output| block } -> obj + # Minitar::Output.open(io) -> output + # Minitar::Output.open(io) { |output| block } -> obj def self.open(output) stream = new(output) return stream unless block_given? @@ -35,8 +33,8 @@ def self.open(output) # will be created with the same behaviour. # # call-seq: - # Archive::Tar::Minitar::Output.tar(io) -> enumerator - # Archive::Tar::Minitar::Output.tar(io) { |tar| block } -> obj + # Minitar::Output.tar(io) -> enumerator + # Minitar::Output.tar(io) { |tar| block } -> obj def self.tar(output) return to_enum(__method__, output) unless block_given? @@ -51,15 +49,15 @@ def self.tar(output) # object wrapped will be closed. # # call-seq: - # Archive::Tar::Minitar::Output.new(io) -> output - # Archive::Tar::Minitar::Output.new(path) -> output + # Minitar::Output.new(io) -> output + # Minitar::Output.new(path) -> output def initialize(output) @io = if output.respond_to?(:write) output else ::Kernel.open(output, "wb") end - @tar = Archive::Tar::Minitar::Writer.new(@io) + @tar = Minitar::Writer.new(@io) end # Returns the Writer object for direct access. diff --git a/lib/minitar/posix_header.rb b/lib/minitar/posix_header.rb new file mode 100644 index 0000000..4ab0369 --- /dev/null +++ b/lib/minitar/posix_header.rb @@ -0,0 +1,267 @@ +class Minitar + # Implements the POSIX tar header as a Ruby class. The structure of + # the POSIX tar header is: + # + # struct tarfile_entry_posix + # { // pack/unpack + # char name[100]; // ASCII (+ Z unless filled) a100/Z100 + # char mode[8]; // 0 padded, octal, null a8 /A8 + # char uid[8]; // 0 padded, octal, null a8 /A8 + # char gid[8]; // 0 padded, octal, null a8 /A8 + # char size[12]; // 0 padded, octal, null a12 /A12 + # char mtime[12]; // 0 padded, octal, null a12 /A12 + # char checksum[8]; // 0 padded, octal, null, space a8 /A8 + # char typeflag[1]; // see below a /a + # char linkname[100]; // ASCII + (Z unless filled) a100/Z100 + # char magic[6]; // "ustar\0" a6 /A6 + # char version[2]; // "00" a2 /A2 + # char uname[32]; // ASCIIZ a32 /Z32 + # char gname[32]; // ASCIIZ a32 /Z32 + # char devmajor[8]; // 0 padded, octal, null a8 /A8 + # char devminor[8]; // 0 padded, octal, null a8 /A8 + # char prefix[155]; // ASCII (+ Z unless filled) a155/Z155 + # }; + # + # The #typeflag is one of several known values. + # + # POSIX indicates that "A POSIX-compliant implementation must treat any + # unrecognized typeflag value as a regular file." + class PosixHeader + BLOCK_SIZE = 512 + MAGIC_BYTES = "ustar".freeze + + GNU_EXT_LONG_LINK = "././@LongLink" + + # Fields that must be set in a POSIX tar(1) header. + REQUIRED_FIELDS = [:name, :size, :prefix, :mode].freeze + # Fields that may be set in a POSIX tar(1) header. + OPTIONAL_FIELDS = [ + :uid, :gid, :mtime, :checksum, :typeflag, :linkname, :magic, :version, + :uname, :gname, :devmajor, :devminor + ].freeze + + # All fields available in a POSIX tar(1) header. + FIELDS = (REQUIRED_FIELDS + OPTIONAL_FIELDS).freeze + + FIELDS.each do |f| + attr_reader f.to_sym unless f.to_sym == :name + end + + # The name of the file. By default, limited to 100 bytes. Required. May be + # longer (up to BLOCK_SIZE bytes) if using the GNU long name tar extension. + attr_accessor :name + + # The pack format passed to Array#pack for encoding a header. + HEADER_PACK_FORMAT = "a100a8a8a8a12a12a7aaa100a6a2a32a32a8a8a155".freeze + # The unpack format passed to String#unpack for decoding a header. + HEADER_UNPACK_FORMAT = "Z100A8A8A8A12A12A8aZ100A6A2Z32Z32A8A8Z155".freeze + + class << self + # Creates a new PosixHeader from a data stream. + def from_stream(stream) + from_data(stream.read(BLOCK_SIZE)) + end + + # Creates a new PosixHeader from a data stream. Deprecated; use + # PosixHeader.from_stream instead. + def new_from_stream(stream) + warn "#{__method__} has been deprecated; use from_stream instead." + from_stream(stream) + end + + # Creates a new PosixHeader from a BLOCK_SIZE-byte data buffer. + def from_data(data) + fields = data.unpack(HEADER_UNPACK_FORMAT) + name = fields.shift + mode = fields.shift.oct + uid = fields.shift.oct + gid = fields.shift.oct + size = strict_oct(fields.shift) + mtime = fields.shift.oct + checksum = fields.shift.oct + typeflag = fields.shift + linkname = fields.shift + magic = fields.shift + version = fields.shift.oct + uname = fields.shift + gname = fields.shift + devmajor = fields.shift.oct + devminor = fields.shift.oct + prefix = fields.shift + + empty = !data.each_byte.any?(&:nonzero?) + + new( + name: name, + mode: mode, + uid: uid, + gid: gid, + size: size, + mtime: mtime, + checksum: checksum, + typeflag: typeflag, + magic: magic, + version: version, + uname: uname, + gname: gname, + devmajor: devmajor, + devminor: devminor, + prefix: prefix, + empty: empty, + linkname: linkname + ) + end + + private + + def strict_oct(string) + return string.oct if /\A[0-7 ]*\z/.match?(string) + raise ArgumentError, "#{string.inspect} is not a valid octal string" + end + end + + # Creates a new PosixHeader. A PosixHeader cannot be created unless + # +name+, +size+, +prefix+, and +mode+ are provided. + def initialize(v) + REQUIRED_FIELDS.each do |f| + raise ArgumentError, "Field #{f} is required." unless v.key?(f) + end + + v[:mtime] = v[:mtime].to_i + v[:checksum] ||= "" + v[:typeflag] ||= "0" + v[:magic] ||= MAGIC_BYTES + v[:version] ||= "00" + + FIELDS.each do |f| + instance_variable_set(:"@#{f}", v[f]) + end + + @empty = v[:empty] + end + + # Indicates if the header was an empty header. + def empty? + @empty + end + + # Indicates if the header has a valid magic value. + def valid? + empty? || @magic == MAGIC_BYTES + end + + # Returns +true+ if the header is a long name special header which indicates + # that the next block of data is the filename. + def long_name? + typeflag == "L" && name == GNU_EXT_LONG_LINK + end + + # A string representation of the header. + def to_s + update_checksum + header(@checksum) + end + alias_method :to_str, :to_s + + # Update the checksum field. + def update_checksum + hh = header(" " * 8) + @checksum = oct(calculate_checksum(hh), 6) + end + + private + + def oct(num, len) + if num.nil? + "\0" * (len + 1) + else + "%0#{len}o" % num + end + end + + def calculate_checksum(hdr) + hdr.unpack("C*").inject { |a, e| a + e } + end + + def header(chksum) + arr = [name, oct(mode, 7), oct(uid, 7), oct(gid, 7), oct(size, 11), + oct(mtime, 11), chksum, " ", typeflag, linkname, magic, version, + uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix] + str = arr.pack(HEADER_PACK_FORMAT) + str + "\0" * ((BLOCK_SIZE - str.bytesize) % BLOCK_SIZE) + end + + ## + # :attr_reader: size + # The size of the file. Required. + + ## + # :attr_reader: prefix + # The prefix of the file; the path before #name. Limited to 155 bytes. + # Required. + + ## + # :attr_reader: mode + # The Unix file mode of the file. Stored as an octal integer. Required. + + ## + # :attr_reader: uid + # The Unix owner user ID of the file. Stored as an octal integer. + + ## + # :attr_reader: uname + # The user name of the Unix owner of the file. + + ## + # :attr_reader: gid + # The Unix owner group ID of the file. Stored as an octal integer. + + ## + # :attr_reader: gname + # The group name of the Unix owner of the file. + + ## + # :attr_reader: mtime + # The modification time of the file in epoch seconds. Stored as an + # octal integer. + + ## + # :attr_reader: checksum + # The checksum of the file. Stored as an octal integer. Calculated + # before encoding the header as a string. + + ## + # :attr_reader: typeflag + # The type of record in the file. + # + # +0+:: Regular file. NULL should be treated as a synonym, for compatibility + # purposes. + # +1+:: Hard link. + # +2+:: Symbolic link. + # +3+:: Character device node. + # +4+:: Block device node. + # +5+:: Directory. + # +6+:: FIFO node. + # +7+:: Reserved. + + ## + # :attr_reader: linkname + # The name of the link stored. Not currently used. + + ## + # :attr_reader: magic + # Always "ustar\0". + + ## + # :attr_reader: version + # Always "00" + + ## + # :attr_reader: devmajor + # The major device ID. Not currently used. + + ## + # :attr_reader: devminor + # The minor device ID. Not currently used. + end +end diff --git a/lib/archive/tar/minitar/reader.rb b/lib/minitar/reader.rb similarity index 84% rename from lib/archive/tar/minitar/reader.rb rename to lib/minitar/reader.rb index 21fdcc2..fda01b8 100644 --- a/lib/archive/tar/minitar/reader.rb +++ b/lib/minitar/reader.rb @@ -1,12 +1,9 @@ -# coding: utf-8 - -module Archive::Tar::Minitar +class Minitar # The class that reads a tar format archive from a data stream. The data # stream may be sequential or random access, but certain features only work # with random access data streams. class Reader include Enumerable - include Archive::Tar::Minitar::ByteSize # This marks the EntryStream closed for reading without closing the # actual data stream. @@ -30,9 +27,7 @@ def closed? # EntryStreams are pseudo-streams on top of the main data stream. class EntryStream - include Archive::Tar::Minitar::ByteSize - - Archive::Tar::Minitar::PosixHeader::FIELDS.each do |field| + Minitar::PosixHeader::FIELDS.each do |field| attr_reader field.to_sym end @@ -56,7 +51,7 @@ def initialize(header, io) @prefix = header.prefix @read = 0 @orig_pos = - if Archive::Tar::Minitar.seekable?(@io) + if Minitar.seekable?(@io) @io.pos else 0 @@ -70,7 +65,7 @@ def read(len = nil) len ||= @size - @read max_read = [len, @size - @read].min ret = @io.read(max_read) - @read += bytesize(ret) + @read += ret.bytesize ret end @@ -119,8 +114,8 @@ def pos # Sets the current read pointer to the beginning of the EntryStream. def rewind - unless Archive::Tar::Minitar.seekable?(@io, :pos=) - raise Archive::Tar::Minitar::NonSeekableStream + unless Minitar.seekable?(@io, :pos=) + raise Minitar::NonSeekableStream end @io.pos = @orig_pos @read = 0 @@ -177,7 +172,7 @@ def self.open(io) # Iterates over each entry in the provided input. This wraps the common # pattern of: # - # Archive::Tar::Minitar::Input.open(io) do |i| + # Minitar::Input.open(io) do |i| # inp.each do |entry| # # ... # end @@ -187,8 +182,8 @@ def self.open(io) # behaviour. # # call-seq: - # Archive::Tar::Minitar::Reader.each_entry(io) -> enumerator - # Archive::Tar::Minitar::Reader.each_entry(io) { |entry| block } -> obj + # Minitar::Reader.each_entry(io) -> enumerator + # Minitar::Reader.each_entry(io) { |entry| block } -> obj def self.each_entry(io) return to_enum(__method__, io) unless block_given? @@ -214,13 +209,13 @@ def initialize(io) # random access data streams that respond to #rewind and #pos. def rewind if @init_pos.zero? - unless Archive::Tar::Minitar.seekable?(@io, :rewind) - raise Archive::Tar::Minitar::NonSeekableStream + unless Minitar.seekable?(@io, :rewind) + raise Minitar::NonSeekableStream end @io.rewind else - unless Archive::Tar::Minitar.seekable?(@io, :pos=) - raise Archive::Tar::Minitar::NonSeekableStream + unless Minitar.seekable?(@io, :pos=) + raise Minitar::NonSeekableStream end @io.pos = @init_pos end @@ -233,11 +228,11 @@ def each_entry loop do return if @io.eof? - header = Archive::Tar::Minitar::PosixHeader.from_stream(@io) - raise Archive::Tar::Minitar::InvalidTarStream unless header.valid? + header = Minitar::PosixHeader.from_stream(@io) + raise Minitar::InvalidTarStream unless header.valid? return if header.empty? - raise Archive::Tar::Minitar::InvalidTarStream if header.size < 0 + raise Minitar::InvalidTarStream if header.size < 0 if header.long_name? name_block = (header.size / 512.0).ceil * 512 @@ -256,13 +251,13 @@ def each_entry skip = (512 - (size % 512)) % 512 - if Archive::Tar::Minitar.seekable?(@io, :seek) + if Minitar.seekable?(@io, :seek) # avoid reading... @io.seek(size - entry.bytes_read, IO::SEEK_CUR) else pending = size - entry.bytes_read while pending > 0 - bread = bytesize(@io.read([pending, 4096].min)) + bread = @io.read([pending, 4096].min).bytesize raise UnexpectedEOF if @io.eof? pending -= bread end diff --git a/lib/archive/tar/minitar/writer.rb b/lib/minitar/writer.rb similarity index 83% rename from lib/archive/tar/minitar/writer.rb rename to lib/minitar/writer.rb index f41cc23..ff17058 100644 --- a/lib/archive/tar/minitar/writer.rb +++ b/lib/minitar/writer.rb @@ -1,10 +1,6 @@ -# coding: utf-8 - -module Archive::Tar::Minitar +class Minitar # The class that writes a tar format archive to a data stream. class Writer - include Archive::Tar::Minitar::ByteSize - # The exception raised when the user attempts to write more data to a # BoundedWriteStream than has been allocated. WriteBoundaryOverflow = Class.new(StandardError) @@ -25,15 +21,13 @@ def write(data) # A WriteOnlyStream that also has a size limit. class BoundedWriteStream < WriteOnlyStream - include Archive::Tar::Minitar::ByteSize - def self.const_missing(c) case c when :FileOverflow warn "Writer::BoundedWriteStream::FileOverflow has been renamed " \ "to Writer::WriteBoundaryOverflow" const_set :FileOverflow, - Archive::Tar::Minitar::Writer::WriteBoundaryOverflow + Minitar::Writer::WriteBoundaryOverflow else super end @@ -52,7 +46,7 @@ def initialize(io, limit) end def write(data) - size = bytesize(data) + size = data.bytesize raise WriteBoundaryOverflow if (size + @written) > @limit @io.write(data) @written += size @@ -79,11 +73,11 @@ def self.const_missing(c) # of the block. # # call-seq: - # w = Archive::Tar::Minitar::Writer.open(STDOUT) + # w = Minitar::Writer.open(STDOUT) # w.add_file_simple('foo.txt', :size => 3) # w.close # - # Archive::Tar::Minitar::Writer.open(STDOUT) do |w| + # Minitar::Writer.open(STDOUT) do |w| # w.add_file_simple('foo.txt', :size => 3) # end def self.open(io) # :yields Writer: @@ -139,10 +133,10 @@ def add_file_simple(name, opts = {}) # :yields BoundedWriteStream: raise ClosedStream if @closed header = { - :mode => opts.fetch(:mode, 0o644), - :mtime => opts.fetch(:mtime, nil), - :gid => opts.fetch(:gid, nil), - :uid => opts.fetch(:uid, nil) + mode: opts.fetch(:mode, 0o644), + mtime: opts.fetch(:mtime, nil), + gid: opts.fetch(:gid, nil), + uid: opts.fetch(:uid, nil) } data = opts.fetch(:data, nil) @@ -158,7 +152,7 @@ def add_file_simple(name, opts = {}) # :yields BoundedWriteStream: else raise ArgumentError, "No data provided" unless data - bytes = bytesize(data) + bytes = data.bytesize size = bytes if size.nil? || size < bytes end @@ -203,13 +197,13 @@ def add_file_simple(name, opts = {}) # :yields BoundedWriteStream: # #add_file_simple must be used. # # +opts+ may be modified during the writing of the file to the stream. - def add_file(name, opts = {}, &block) # :yields WriteOnlyStream, +opts+: + def add_file(name, opts = {}, &) # :yields WriteOnlyStream, +opts+: raise ClosedStream if @closed - return add_file_simple(name, opts, &block) if opts[:data] + return add_file_simple(name, opts, &) if opts[:data] - unless Archive::Tar::Minitar.seekable?(@io) - raise Archive::Tar::Minitar::NonSeekableStream + unless Minitar.seekable?(@io) + raise Minitar::NonSeekableStream end short_name, prefix, needs_long_name = split_name(name) @@ -227,11 +221,11 @@ def add_file(name, opts = {}, &block) # :yields WriteOnlyStream, +opts+: final_pos, @io.pos = @io.pos, init_pos header = { - :mode => opts[:mode], - :mtime => opts[:mtime], - :size => size, - :gid => opts[:gid], - :uid => opts[:uid] + mode: opts[:mode], + mtime: opts[:mtime], + size: size, + gid: opts[:gid], + uid: opts[:uid] } write_header(header, name, short_name, prefix, needs_long_name) @@ -244,12 +238,12 @@ def mkdir(name, opts = {}) raise ClosedStream if @closed header = { - :mode => opts[:mode], - :typeflag => "5", - :size => 0, - :gid => opts[:gid], - :uid => opts[:uid], - :mtime => opts[:mtime] + mode: opts[:mode], + typeflag: "5", + size: 0, + gid: opts[:gid], + uid: opts[:uid], + mtime: opts[:mtime] } short_name, prefix, needs_long_name = split_name(name) @@ -266,15 +260,15 @@ def symlink(name, link_target, opts = {}) name, prefix = split_name(name) header = { - :name => name, - :mode => opts[:mode], - :typeflag => "2", - :size => 0, - :linkname => link_target, - :gid => opts[:gid], - :uid => opts[:uid], - :mtime => opts[:mtime], - :prefix => prefix + name: name, + mode: opts[:mode], + typeflag: "2", + size: 0, + linkname: link_target, + gid: opts[:gid], + uid: opts[:uid], + mtime: opts[:mtime], + prefix: prefix } @io.write(PosixHeader.new(header)) nil @@ -305,23 +299,23 @@ def close def write_header(header, long_name, short_name, prefix, needs_long_name) if needs_long_name long_name_header = { - :prefix => "", - :name => PosixHeader::GNU_EXT_LONG_LINK, - :typeflag => "L", - :size => long_name.length + 1, - :mode => 0 + prefix: "", + name: PosixHeader::GNU_EXT_LONG_LINK, + typeflag: "L", + size: long_name.length + 1, + mode: 0 } @io.write(PosixHeader.new(long_name_header)) @io.write(long_name) @io.write("\0" * (512 - (long_name.length % 512))) end - new_header = header.merge({:name => short_name, :prefix => prefix}) + new_header = header.merge({name: short_name, prefix: prefix}) @io.write(PosixHeader.new(new_header)) end def split_name(name) - if bytesize(name) <= 100 + if name.bytesize <= 100 prefix = "" else parts = name.split("/") @@ -331,7 +325,7 @@ def split_name(name) loop do nxt = parts.pop || "" - break if bytesize(newname) + 1 + bytesize(nxt) >= 100 + break if newname.bytesize + 1 + nxt.bytesize >= 100 newname = "#{nxt}/#{newname}" end @@ -340,7 +334,7 @@ def split_name(name) name = newname end - [name, prefix, bytesize(name) > 100 || bytesize(prefix) > 155] + [name, prefix, name.bytesize > 100 || prefix.bytesize > 155] end end end diff --git a/minitar.gemspec b/minitar.gemspec index 112c2c6..c439be7 100644 --- a/minitar.gemspec +++ b/minitar.gemspec @@ -1,40 +1,41 @@ # -*- encoding: utf-8 -*- -# stub: minitar 0.12 ruby lib +# stub: minitar 1.0.0 ruby lib Gem::Specification.new do |s| s.name = "minitar".freeze - s.version = "0.12".freeze + s.version = "1.0.0" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "bug_tracker_uri" => "https://github.com/halostatue/minitar/issues", "homepage_uri" => "https://github.com/halostatue/minitar/", "rubygems_mfa_required" => "true", "source_code_uri" => "https://github.com/halostatue/minitar/" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Austin Ziegler".freeze] - s.date = "2024-08-06" + s.date = "2024-08-07" s.description = "The minitar library is a pure-Ruby library that provides the ability to deal\nwith POSIX tar(1) archive files.\n\nThis is release 0.12. This is likely the last revision before 1.0.\n\nminitar (previously called Archive::Tar::Minitar) is based heavily on code\noriginally written by Mauricio Julio Fern\u00E1ndez Pradier for the rpa-base\nproject.".freeze s.email = ["halostatue@gmail.com".freeze] s.extra_rdoc_files = ["Code-of-Conduct.md".freeze, "Contributing.md".freeze, "History.md".freeze, "Licence.md".freeze, "Manifest.txt".freeze, "README.rdoc".freeze, "docs/bsdl.txt".freeze, "docs/ruby.txt".freeze] - s.files = ["Code-of-Conduct.md".freeze, "Contributing.md".freeze, "History.md".freeze, "Licence.md".freeze, "Manifest.txt".freeze, "README.rdoc".freeze, "Rakefile".freeze, "docs/bsdl.txt".freeze, "docs/ruby.txt".freeze, "lib/archive-tar-minitar.rb".freeze, "lib/archive/tar/minitar.rb".freeze, "lib/archive/tar/minitar/input.rb".freeze, "lib/archive/tar/minitar/output.rb".freeze, "lib/archive/tar/minitar/posix_header.rb".freeze, "lib/archive/tar/minitar/reader.rb".freeze, "lib/archive/tar/minitar/writer.rb".freeze, "lib/minitar.rb".freeze, "support/hoe/deprecated_gem.rb".freeze, "test/minitest_helper.rb".freeze, "test/support/tar_test_helpers.rb".freeze, "test/test_tar_header.rb".freeze, "test/test_tar_input.rb".freeze, "test/test_tar_output.rb".freeze, "test/test_tar_reader.rb".freeze, "test/test_tar_writer.rb".freeze] + s.files = ["Code-of-Conduct.md".freeze, "Contributing.md".freeze, "History.md".freeze, "Licence.md".freeze, "Manifest.txt".freeze, "README.rdoc".freeze, "Rakefile".freeze, "docs/bsdl.txt".freeze, "docs/ruby.txt".freeze, "lib/minitar.rb".freeze, "lib/minitar/input.rb".freeze, "lib/minitar/output.rb".freeze, "lib/minitar/posix_header.rb".freeze, "lib/minitar/reader.rb".freeze, "lib/minitar/writer.rb".freeze, "renovate.json".freeze, "test/minitest_helper.rb".freeze, "test/support/tar_test_helpers.rb".freeze, "test/test_issue_46.rb".freeze, "test/test_tar_header.rb".freeze, "test/test_tar_input.rb".freeze, "test/test_tar_output.rb".freeze, "test/test_tar_reader.rb".freeze, "test/test_tar_writer.rb".freeze] s.homepage = "https://github.com/halostatue/minitar/".freeze s.licenses = ["Ruby".freeze, "BSD-2-Clause".freeze] - s.post_install_message = "The `minitar` executable is no longer bundled with `minitar`. If you are\nexpecting this executable, make sure you also install `minitar-cli`.\n".freeze s.rdoc_options = ["--main".freeze, "README.rdoc".freeze] - s.required_ruby_version = Gem::Requirement.new(">= 1.8".freeze) - s.rubygems_version = "3.5.17".freeze + s.required_ruby_version = Gem::Requirement.new(">= 3.1".freeze) + s.rubygems_version = "3.4.19".freeze s.summary = "The minitar library is a pure-Ruby library that provides the ability to deal with POSIX tar(1) archive files".freeze s.specification_version = 4 - s.add_development_dependency(%q.freeze, ["~> 5.24".freeze]) - s.add_development_dependency(%q.freeze, ["~> 0.2".freeze]) - s.add_development_dependency(%q.freeze, ["~> 4.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.1".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.7".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) - s.add_development_dependency(%q.freeze, [">= 10.0".freeze, "< 14".freeze]) - s.add_development_dependency(%q.freeze, [">= 0.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 0.21".freeze]) + s.add_development_dependency(%q.freeze, ["~> 5.24"]) + s.add_development_dependency(%q.freeze, ["~> 0.2"]) + s.add_development_dependency(%q.freeze, ["~> 4.0"]) + s.add_development_dependency(%q.freeze, ["~> 1.0"]) + s.add_development_dependency(%q.freeze, ["~> 1.1"]) + s.add_development_dependency(%q.freeze, ["~> 1.7"]) + s.add_development_dependency(%q.freeze, ["~> 1.0"]) + s.add_development_dependency(%q.freeze, ["~> 1.0"]) + s.add_development_dependency(%q.freeze, ["~> 1.0"]) + s.add_development_dependency(%q.freeze, [">= 10.0", "< 14"]) + s.add_development_dependency(%q.freeze, [">= 0.0"]) + s.add_development_dependency(%q.freeze, ["~> 1.0"]) + s.add_development_dependency(%q.freeze, ["~> 1.0"]) + s.add_development_dependency(%q.freeze, ["~> 1.0"]) + s.add_development_dependency(%q.freeze, ["~> 0.21"]) end diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 5db72dd..0000000 --- a/renovate.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended" - ] -} diff --git a/support/hoe/deprecated_gem.rb b/support/hoe/deprecated_gem.rb deleted file mode 100644 index 8b5e216..0000000 --- a/support/hoe/deprecated_gem.rb +++ /dev/null @@ -1,66 +0,0 @@ -# A Hoe plug-in to provide a second, linked gemspec, for a gem that has been -# deprecated in favour of a modern name. (The name is an artifact of Hoe's -# plugin loading.) -# -# TODO: Remove for 1.0 since archive-tar-minitar is going away -module Hoe::Deprecated_Gem # standard:disable Naming/ClassAndModuleCamelCase - def linked_spec(spec) - permitted_classes = %w[ - Symbol Time Date Gem::Dependency Gem::Platform Gem::Requirement - Gem::Specification Gem::Version Gem::Version::Requirement - YAML::Syck::DefaultKey Syck::DefaultKey - ] - permitted_symbols = %w[development runtime] - atm = begin - YAML.safe_load( - YAML.dump(spec), - :permitted_classes => permitted_classes, - :permitted_symbols => permitted_symbols, - :aliases => true - ) - rescue - YAML.safe_load( - YAML.dump(spec), permitted_classes, permitted_symbols, true - ) - end - atm.name = "archive-tar-minitar" - d = %('#{atm.name}' has been deprecated; just install '#{spec.name}'.) - atm.description = "#{d} #{spec.description}" - atm.summary = atm.post_install_message = d - atm.files.delete_if do |f| - f !~ %r{lib/archive-tar-minitar\.rb} - end - atm.extra_rdoc_files.clear - atm.rdoc_options.clear - atm.dependencies.clear - - version = Gem::Version.new(spec.version.segments.first(2).join(".")) - - atm.add_dependency(spec.name, "~> #{version}") - atm.add_dependency(%(#{spec.name}-cli), "~> #{version}") - - unless @include_all - [:signing_key, :cert_chain].each { |name| - atm.send(:"#{name}=", atm.default_value(name)) - } - end - - atm - end - - def define_deprecated_gem_tasks - gemspec = spec.name + ".gemspec" - atmspec = "archive-tar-minitar.gemspec" - - file atmspec => gemspec do - File.open(atmspec, "w") { |f| f.write(linked_spec(spec).to_ruby) } - end - - task :gemspec => atmspec - - Gem::PackageTask.new linked_spec(spec) do |pkg| - pkg.need_tar = @need_tar - pkg.need_zip = @need_zip - end - end -end diff --git a/test/minitest_helper.rb b/test/minitest_helper.rb index 3cb935c..6a268d9 100644 --- a/test/minitest_helper.rb +++ b/test/minitest_helper.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - require "fileutils" require "minitar" diff --git a/test/support/tar_test_helpers.rb b/test/support/tar_test_helpers.rb index e082464..9cba0ba 100644 --- a/test/support/tar_test_helpers.rb +++ b/test/support/tar_test_helpers.rb @@ -1,13 +1,12 @@ -# frozen_string_literal: true - module TarTestHelpers - include Archive::Tar::Minitar::ByteSize - Field = Struct.new(:name, :offset, :length) def self.Field(name, length) + # standard:disable ThreadSafety/InstanceVariableInClassMethod @offset ||= 0 field = Field.new(name, @offset, length) @offset += length + # standard:enable ThreadSafety/InstanceVariableInClassMethod + FIELDS[name] = field FIELD_ORDER << name field @@ -51,7 +50,7 @@ def assert_headers_equal(expected, actual) offset = FIELDS[field].offset length = FIELDS[field].length - assert_equal(expected[offset, length], actual[offset, length], message) + assert_equal expected[offset, length], actual[offset, length], message end end @@ -95,8 +94,8 @@ def raw_header(type, fname, dname, length, mode, link_name = "") ] h = arr.join.bytes.to_a.pack("C100C8C8C8C12C12C8CC100C6C2C32C32C8C8C155") - ret = h + "\0" * (512 - bytesize(h)) - assert_equal(512, bytesize(ret)) + ret = h + "\0" * (512 - h.bytesize) + assert_equal 512, ret.bytesize ret end @@ -116,7 +115,7 @@ def to_oct(n, pad_size) end def asciiz(str, length) - str + "\0" * (length - bytesize(str)) + str + "\0" * (length - str.bytesize) end def sp(s) diff --git a/test/test_issue_46.rb b/test/test_issue_46.rb index 0c84621..afeb487 100755 --- a/test/test_issue_46.rb +++ b/test/test_issue_46.rb @@ -6,12 +6,12 @@ require "zlib" class TestIssue46 < Minitest::Test - SUPERLONG_TGZ = Base64.decode64(<<-EOS).freeze -H4sIAK1+smYAA+3WQQ6CMBAF0K49BScAprYd3XkALoECSiQlQYzXt0IkSKLGBdXE -/zbtNF000PkQRmG0SWq7T0p7FPOIHaNUNzrTkWI5zPt1IiYtgmSm8zw4n9q0CQLR -1HX7at/lkOeVjwP5FZNcKm14tU63uyyPUP91/e3rCJ75uF/j/Gej+6yXw/fArbnM -Z2ZlDKlb/ktNrEQQ+3gA9/xP3aS0z/e5bUXh40B+/Vj+oJ63Xkzff26zoqzmzf13 -/d/98437n0izQf8DAAAAAAAAAAAAAAAAAHziCqQuXDYAKAAA + SUPERLONG_TGZ = Base64.decode64(<<~EOS).freeze + H4sIAK1+smYAA+3WQQ6CMBAF0K49BScAprYd3XkALoECSiQlQYzXt0IkSKLGBdXE + /zbtNF000PkQRmG0SWq7T0p7FPOIHaNUNzrTkWI5zPt1IiYtgmSm8zw4n9q0CQLR + 1HX7at/lkOeVjwP5FZNcKm14tU63uyyPUP91/e3rCJ75uF/j/Gej+6yXw/fArbnM + Z2ZlDKlb/ktNrEQQ+3gA9/xP3aS0z/e5bUXh40B+/Vj+oJ63Xkzff26zoqzmzf13 + /d/98437n0izQf8DAAAAAAAAAAAAAAAAAHziCqQuXDYAKAAA EOS FILETIMES = Time.utc(2004).to_i @@ -19,8 +19,8 @@ class TestIssue46 < Minitest::Test superlong_name = (["0123456789abcde"] * 33).join("/") SUPERLONG_CONTENTS = { - superlong_name => {:size => 496, :mode => 0o644}, - "endfile" => {:size => 0, :mode => 0o644} + superlong_name => {size: 496, mode: 0o644}, + "endfile" => {size: 0, mode: 0o644} } def test_each_works @@ -38,12 +38,12 @@ def test_each_works assert_modes_equal(SUPERLONG_CONTENTS[entry.name][:mode], entry.mode, entry.name) - assert_equal(FILETIMES, entry.mtime, "entry.mtime") + assert_equal FILETIMES, entry.mtime, "entry.mtime" outer += 1 end - assert_equal(2, outer) + assert_equal 2, outer end end end diff --git a/test/test_minitar.rb b/test/test_minitar.rb new file mode 100755 index 0000000..313d675 --- /dev/null +++ b/test/test_minitar.rb @@ -0,0 +1,60 @@ +#!/usr/bin/env ruby + +require "minitar" +require "minitest_helper" +require "zlib" + +class TestMinitar < Minitest::Test + FILE_2004 = Time.utc(2004).to_i + + def test_pack_as_file + input = [ + ["path", nil], + ["test", "test"], + ["extra/test", "extra/test"], + [{name: "empty2004", mtime: FILE_2004, mode: 0o755}, ""] + ] + + writer = StringIO.new + Minitar::Output.open(writer) do |out_stream| + input.each do |(name, data)| + Minitar.pack_as_file(name, data, out_stream) + end + end + + expected = [ + {name: "path", size: 0, mode: 0o755}, + {name: "test", size: 4, mode: 0o644, data: "test"}, + {name: "extra/test", size: 10, mode: 0o0644, data: "extra/test"}, + {name: "empty2004", size: 0, mode: 0o755, mtime: FILE_2004, nil: true} + ] + + count = 0 + reader = StringIO.new(writer.string) + Minitar::Input.open(reader) do |stream| + stream.each.with_index do |entry, i| + assert_kind_of Minitar::Reader::EntryStream, entry + + assert_equal expected[i][:name], entry.name + assert_equal expected[i][:size], entry.size + assert_equal expected[i][:mode], entry.mode + + if expected[i].key?(:mtime) + assert_equal expected[i][:mtime], entry.mtime + end + + if expected[i].key?(:data) + assert_equal expected[i][:data], entry.read + end + + if expected[i].key?(:nil) + assert_nil entry.read + end + + count += 1 + end + end + + assert_equal expected.size, count + end +end diff --git a/test/test_tar_header.rb b/test/test_tar_header.rb index 5307fde..22ac68d 100644 --- a/test/test_tar_header.rb +++ b/test/test_tar_header.rb @@ -1,81 +1,79 @@ -# frozen_string_literal: true - require "minitest_helper" class TestTarHeader < Minitest::Test def test_arguments_are_checked - ph = Archive::Tar::Minitar::PosixHeader + ph = Minitar::PosixHeader assert_raises(ArgumentError) { - ph.new(:name => "", :size => "", :mode => "") + ph.new(name: "", size: "", mode: "") } assert_raises(ArgumentError) { - ph.new(:name => "", :size => "", :prefix => "") + ph.new(name: "", size: "", prefix: "") } assert_raises(ArgumentError) { - ph.new(:name => "", :prefix => "", :mode => "") + ph.new(name: "", prefix: "", mode: "") } assert_raises(ArgumentError) { - ph.new(:prefix => "", :size => "", :mode => "") + ph.new(prefix: "", size: "", mode: "") } end def test_basic_headers header = { - :name => "bla", - :mode => 0o12345, - :size => 10, - :prefix => "", - :typeflag => "0" + name: "bla", + mode: 0o12345, + size: 10, + prefix: "", + typeflag: "0" } assert_headers_equal(tar_file_header("bla", "", 0o12345, 10), - Archive::Tar::Minitar::PosixHeader.new(header).to_s) + Minitar::PosixHeader.new(header).to_s) header = { - :name => "bla", - :mode => 0o12345, - :size => 0, - :prefix => "", - :typeflag => "5" + name: "bla", + mode: 0o12345, + size: 0, + prefix: "", + typeflag: "5" } assert_headers_equal(tar_dir_header("bla", "", 0o12345), - Archive::Tar::Minitar::PosixHeader.new(header).to_s) + Minitar::PosixHeader.new(header).to_s) end def test_long_name_works header = { - :name => "a" * 100, :mode => 0o12345, :size => 10, :prefix => "" + name: "a" * 100, mode: 0o12345, size: 10, prefix: "" } assert_headers_equal(tar_file_header("a" * 100, "", 0o12345, 10), - Archive::Tar::Minitar::PosixHeader.new(header).to_s) + Minitar::PosixHeader.new(header).to_s) header = { - :name => "a" * 100, :mode => 0o12345, :size => 10, :prefix => "bb" * 60 + name: "a" * 100, mode: 0o12345, size: 10, prefix: "bb" * 60 } assert_headers_equal(tar_file_header("a" * 100, "bb" * 60, 0o12345, 10), - Archive::Tar::Minitar::PosixHeader.new(header).to_s) + Minitar::PosixHeader.new(header).to_s) end def test_from_stream header = tar_file_header("a" * 100, "", 0o12345, 10) header = StringIO.new(header) - h = Archive::Tar::Minitar::PosixHeader.from_stream(header) - assert_equal("a" * 100, h.name) - assert_equal(0o12345, h.mode) - assert_equal(10, h.size) - assert_equal("", h.prefix) - assert_equal("ustar", h.magic) + h = Minitar::PosixHeader.from_stream(header) + assert_equal "a" * 100, h.name + assert_equal 0o12345, h.mode + assert_equal 10, h.size + assert_equal "", h.prefix + assert_equal "ustar", h.magic end def test_from_stream_with_evil_name header = tar_file_header("a \0" + "\0" * 97, "", 0o12345, 10) header = StringIO.new(header) - h = Archive::Tar::Minitar::PosixHeader.from_stream header - assert_equal("a ", h.name) + h = Minitar::PosixHeader.from_stream header + assert_equal "a ", h.name end def test_valid_with_valid_header header = tar_file_header("a" * 100, "", 0o12345, 10) header = StringIO.new(header) - h = Archive::Tar::Minitar::PosixHeader.from_stream header + h = Minitar::PosixHeader.from_stream header assert(h.valid?) end @@ -85,7 +83,7 @@ def test_from_stream_with_no_strict_octal io = StringIO.new(header) assert_raises(ArgumentError) do - Archive::Tar::Minitar::PosixHeader.from_stream(io) + Minitar::PosixHeader.from_stream(io) end end @@ -98,14 +96,14 @@ def test_from_stream_with_octal_wrapped_by_spaces header = update_checksum(header) io = StringIO.new(header) - header = Archive::Tar::Minitar::PosixHeader.from_stream(io) + header = Minitar::PosixHeader.from_stream(io) - assert_equal(651, header.size) + assert_equal 651, header.size end def test_valid_with_invalid_header header = StringIO.new("testing") - h = Archive::Tar::Minitar::PosixHeader.from_stream header + h = Minitar::PosixHeader.from_stream header refute(h.valid?) end diff --git a/test/test_tar_input.rb b/test/test_tar_input.rb index d4c2f15..90d49ea 100644 --- a/test/test_tar_input.rb +++ b/test/test_tar_input.rb @@ -6,28 +6,28 @@ require "zlib" class TestTarInput < Minitest::Test - TEST_TGZ = Base64.decode64(<<-EOS).freeze -H4sIAKJpllQAA0tJLEnUK0ks0kuvYqAVMDAwMDMxUQDR5mbmYNrACMIHA2MjIwUDc3NzEzMz -QxMDAwUDQ2NTczMGBQOauQgJlBYDfQ90SiKQkZmHWx1QWVoaHnMgXlGA00MEyHdzMMzOnBbC -wPz28n2uJgOR44Xrq7tsHc/utNe/9FdihkmH3pZ7+zOTRFREzkzYJ99iHHDn4n0/Wb3E8Ceq -S0uOdSyMMg9Z+WVvX0vJucxs77vrvZf2arWcvHP9wa1Yp9lRnJmC59/P9+43PXum+tj7Ga+8 -rtT+u3d941e765Y/bOrnvpv8X6jtz+wKqyk/v3n8P5xlO3l/1dn9q9Zotpy5funw/Of77Y/5 -LVltz7ToTl7dXf5ppmf3n9p+PPxz/sz/qjZn9yf9Y4R7I2Ft3tqfPTUMGgMYlEMSpGXmpBrT -2A5Qvjc1xZ3/DTDyv5GJmfFo/qcHCMnILFYAIlA6UDDWU+DlGmgXjYJRMApGwSgYBaNgFIyC -UTAKRsEoGAWjYBSMglEwCkbBKBgFo2AUjIJRMApGwSgYBaNgFIwCUgAAGnyo6wAoAAA= + TEST_TGZ = Base64.decode64(<<~EOS).freeze + H4sIAKJpllQAA0tJLEnUK0ks0kuvYqAVMDAwMDMxUQDR5mbmYNrACMIHA2MjIwUDc3NzEzMz + QxMDAwUDQ2NTczMGBQOauQgJlBYDfQ90SiKQkZmHWx1QWVoaHnMgXlGA00MEyHdzMMzOnBbC + wPz28n2uJgOR44Xrq7tsHc/utNe/9FdihkmH3pZ7+zOTRFREzkzYJ99iHHDn4n0/Wb3E8Ceq + S0uOdSyMMg9Z+WVvX0vJucxs77vrvZf2arWcvHP9wa1Yp9lRnJmC59/P9+43PXum+tj7Ga+8 + rtT+u3d941e765Y/bOrnvpv8X6jtz+wKqyk/v3n8P5xlO3l/1dn9q9Zotpy5funw/Of77Y/5 + LVltz7ToTl7dXf5ppmf3n9p+PPxz/sz/qjZn9yf9Y4R7I2Ft3tqfPTUMGgMYlEMSpGXmpBrT + 2A5Qvjc1xZ3/DTDyv5GJmfFo/qcHCMnILFYAIlA6UDDWU+DlGmgXjYJRMApGwSgYBaNgFIyC + UTAKRsEoGAWjYBSMglEwCkbBKBgFo2AUjIJRMApGwSgYBaNgFIwCUgAAGnyo6wAoAAA= EOS FILETIMES = Time.utc(2004).to_i TEST_CONTENTS = { - "data.tar.gz" => {:size => 210, :mode => 0o644}, - "file3" => {:size => 18, :mode => 0o755} + "data.tar.gz" => {size: 210, mode: 0o644}, + "file3" => {size: 18, mode: 0o755} }.freeze TEST_DATA_CONTENTS = { - "data/" => {:size => 0, :mode => 0o755}, - "data/__dir__/" => {:size => 0, :mode => 0o755}, - "data/file1" => {:size => 16, :mode => 0o644}, - "data/file2" => {:size => 16, :mode => 0o644} + "data/" => {size: 0, mode: 0o755}, + "data/__dir__/" => {size: 0, mode: 0o755}, + "data/file1" => {size: 16, mode: 0o644}, + "data/file2" => {size: 16, mode: 0o644} }.freeze def setup @@ -52,36 +52,36 @@ def test_each_works Minitar::Input.open(reader) do |stream| outer = 0 stream.each.with_index do |entry, i| - assert_kind_of(Minitar::Reader::EntryStream, entry) + assert_kind_of Minitar::Reader::EntryStream, entry assert TEST_CONTENTS.key?(entry.name) - assert_equal(TEST_CONTENTS[entry.name][:size], entry.size, entry.name) + assert_equal TEST_CONTENTS[entry.name][:size], entry.size, entry.name assert_modes_equal(TEST_CONTENTS[entry.name][:mode], entry.mode, entry.name) - assert_equal(FILETIMES, entry.mtime, "entry.mtime") + assert_equal FILETIMES, entry.mtime, "entry.mtime" if i.zero? data_reader = Zlib::GzipReader.new(StringIO.new(entry.read)) Minitar::Input.open(data_reader) do |is2| inner = 0 is2.each_with_index do |entry2, _j| - assert_kind_of(Minitar::Reader::EntryStream, entry2) + assert_kind_of Minitar::Reader::EntryStream, entry2 assert TEST_DATA_CONTENTS.key?(entry2.name) assert_equal(TEST_DATA_CONTENTS[entry2.name][:size], entry2.size, entry2.name) assert_modes_equal(TEST_DATA_CONTENTS[entry2.name][:mode], entry2.mode, entry2.name) - assert_equal(FILETIMES, entry2.mtime, entry2.name) + assert_equal FILETIMES, entry2.mtime, entry2.name inner += 1 end - assert_equal(4, inner) + assert_equal 4, inner end end outer += 1 end - assert_equal(2, outer) + assert_equal 2, outer end end @@ -100,7 +100,7 @@ def test_extract_entry_works else assert(File.file?(name)) - assert_equal(TEST_CONTENTS[entry.name][:size], File.stat(name).size) + assert_equal TEST_CONTENTS[entry.name][:size], File.stat(name).size end assert_modes_equal(TEST_CONTENTS[entry.name][:mode], @@ -136,7 +136,7 @@ def test_extract_entry_works outer_count += 1 end - assert_equal(2, outer_count) + assert_equal 2, outer_count end end @@ -145,7 +145,7 @@ def test_extract_entry_breaks_symlinks IO.respond_to?(:write) && IO.write("data__/file4", "") || - File.open("data__/file4", "w") { |f| f.write "" } + File.write("data__/file4", "") File.symlink("data__/file4", "data__/file3") File.symlink("data__/file4", "data__/data") @@ -158,33 +158,33 @@ def test_extract_entry_breaks_symlinks refute File.symlink?("data__/data") end - RELATIVE_DIRECTORY_TGZ = Base64.decode64 <<-EOS -H4sICIIoKVgCA2JhZC1kaXIudGFyANPT0y8sTy0qqWSgHTAwMDAzMVEA0eZmpmDawAjChwEFQ2MDQyMg -MDUzVDAwNDY0N2VQMGCgAygtLkksAjolEcjIzMOtDqgsLQ2/J0H+gNOjYBSMglEwyAEA2LchrwAGAAA= + RELATIVE_DIRECTORY_TGZ = Base64.decode64 <<~EOS + H4sICIIoKVgCA2JhZC1kaXIudGFyANPT0y8sTy0qqWSgHTAwMDAzMVEA0eZmpmDawAjChwEFQ2MDQyMg + MDUzVDAwNDY0N2VQMGCgAygtLkksAjolEcjIzMOtDqgsLQ2/J0H+gNOjYBSMglEwyAEA2LchrwAGAAA= EOS def test_extract_entry_fails_with_relative_directory reader = Zlib::GzipReader.new(StringIO.new(RELATIVE_DIRECTORY_TGZ)) Minitar::Input.open(reader) do |stream| stream.each do |entry| - assert_raises Archive::Tar::Minitar::SecureRelativePathError do + assert_raises Minitar::SecureRelativePathError do stream.extract_entry("data__", entry) end end end end - NON_STRICT_OCTAL_TGZ = Base64.decode64(<<-EOS).freeze -H4sIAEk55FsAA0tJLEnUK0ks0kuvYqAVMDAwMDMxUQDR5mbmYNrACMjX0zMH -AzMDUwUDIG1iZmZoYmCgYGBobGpuxqBgQDMXIYHSYqDvgU5KBDIy83CrAypL -S8NjjgEYKMDpIQLkuzkYZmdOC2Fgfnv5PleTgcjxwvXVXbaOZ3fa61/6KzHD -pENvy739mUkiKiJnJuyTbzEOuHPxvp+sXmL4E9WlJcc6FkaZh6z8srevpeRc -Zrb33fXeS3u1Wk7euf7gVqzT7CjOTMHz7+d795uePVN97P2MV15Xav/du77x -q911yx829XPfTf4v1PZndoXVlJ/fPP4fzrKdvL/q7P5VazRbzly/dHj+8/32 -x/yWrLZnWnQnr+4u/zTTs/tPbT8e/jl/5n9Vm7P7k/4xwr2RsDZv7c+eGgaN -AQzKIQnSMnNSjWlsByjfm5pi5n8DGDAyQsv/RiZmxqP5nx4gJCOzWAGIQOlA -wVhPgZdroF00CkbBKBgFo2AUjIJRMApGwSgYBaNgFIyCUTAKRsEoGAWjYBSM -glEwCkbBKBgFo2AUjIJRMApIAQD0DyzXACgAAA== + NON_STRICT_OCTAL_TGZ = Base64.decode64(<<~EOS).freeze + H4sIAEk55FsAA0tJLEnUK0ks0kuvYqAVMDAwMDMxUQDR5mbmYNrACMjX0zMH + AzMDUwUDIG1iZmZoYmCgYGBobGpuxqBgQDMXIYHSYqDvgU5KBDIy83CrAypL + S8NjjgEYKMDpIQLkuzkYZmdOC2Fgfnv5PleTgcjxwvXVXbaOZ3fa61/6KzHD + pENvy739mUkiKiJnJuyTbzEOuHPxvp+sXmL4E9WlJcc6FkaZh6z8srevpeRc + Zrb33fXeS3u1Wk7euf7gVqzT7CjOTMHz7+d795uePVN97P2MV15Xav/du77x + q911yx829XPfTf4v1PZndoXVlJ/fPP4fzrKdvL/q7P5VazRbzly/dHj+8/32 + x/yWrLZnWnQnr+4u/zTTs/tPbT8e/jl/5n9Vm7P7k/4xwr2RsDZv7c+eGgaN + AQzKIQnSMnNSjWlsByjfm5pi5n8DGDAyQsv/RiZmxqP5nx4gJCOzWAGIQOlA + wVhPgZdroF00CkbBKBgFo2AUjIJRMApGwSgYBaNgFIyCUTAKRsEoGAWjYBSM + glEwCkbBKBgFo2AUjIJRMApIAQD0DyzXACgAAA== EOS def test_extract_with_non_strict_octal @@ -195,23 +195,23 @@ def test_extract_with_non_strict_octal end end - OCTAL_WRAPPED_BY_SPACE_TGZ = Base64.decode64(<<-EOS).freeze -H4sIAOQg5FsAA0tJLEnUK0ks0kuvYqAVMDAwMDMxUQDR5mbmYNrACMhXgAJj -IyMFA3NzcxMzM0MTAwMFA0NjU3MzBgUDmrkICZQWA30PdFIikJGZh1sdUFla -Gh5zDMBAAU4PESDfzcEwO3NaCAPz28v3uZoMRI4Xrq/usnU8u9Ne/9JfiRkm -HXpb7u3PTBJRETkzYZ98i3HAnYv3/WT1EsOfqC4tOdaxMMo8ZOWXvX0tJecy -s73vrvde2qvVcvLO9Qe3Yp1mR3FmCp5/P9+73/Tsmepj72e88rpS++/e9Y1f -7a5b/rCpn/tu8n+htj+zK6ym/Pzm8f9wlu3k/VVn969ao9ly5vqlw/Of77c/ -5rdktT3Tojt5dXf5p5me3X9q+/Hwz/kz/6vanN2f9I8R7o2EtXlrf/bUMGgM -YFAOSZCWmZNqTGM7QPne1BQz/xvAAEb+NzIxMx7N//QAIRmZxQpABEoHCsZ6 -CrxcA+2iUTAKRsEoGAWjYBSMglEwCkbBKBgFo2AUjIJRMApGwSgYBaNgFIyC -UTAKRsEoGAWjYBSMglFACgAAuUHUvwAoAAA= + OCTAL_WRAPPED_BY_SPACE_TGZ = Base64.decode64(<<~EOS).freeze + H4sIAOQg5FsAA0tJLEnUK0ks0kuvYqAVMDAwMDMxUQDR5mbmYNrACMhXgAJj + IyMFA3NzcxMzM0MTAwMFA0NjU3MzBgUDmrkICZQWA30PdFIikJGZh1sdUFla + Gh5zDMBAAU4PESDfzcEwO3NaCAPz28v3uZoMRI4Xrq/usnU8u9Ne/9JfiRkm + HXpb7u3PTBJRETkzYZ98i3HAnYv3/WT1EsOfqC4tOdaxMMo8ZOWXvX0tJecy + s73vrvde2qvVcvLO9Qe3Yp1mR3FmCp5/P9+73/Tsmepj72e88rpS++/e9Y1f + 7a5b/rCpn/tu8n+htj+zK6ym/Pzm8f9wlu3k/VVn969ao9ly5vqlw/Of77c/ + 5rdktT3Tojt5dXf5p5me3X9q+/Hwz/kz/6vanN2f9I8R7o2EtXlrf/bUMGgM + YFAOSZCWmZNqTGM7QPne1BQz/xvAAEb+NzIxMx7N//QAIRmZxQpABEoHCsZ6 + CrxcA+2iUTAKRsEoGAWjYBSMglEwCkbBKBgFo2AUjIJRMApGwSgYBaNgFIyC + UTAKRsEoGAWjYBSMglFACgAAuUHUvwAoAAA= EOS def test_extract_octal_wrapped_by_space reader = Zlib::GzipReader.new(StringIO.new(OCTAL_WRAPPED_BY_SPACE_TGZ)) - header = Archive::Tar::Minitar::PosixHeader.from_stream(reader) - assert_equal(210, header.size) + header = Minitar::PosixHeader.from_stream(reader) + assert_equal 210, header.size reader = Zlib::GzipReader.new(StringIO.new(OCTAL_WRAPPED_BY_SPACE_TGZ)) Minitar.unpack(reader, "data__", []) @@ -219,9 +219,9 @@ def test_extract_octal_wrapped_by_space def test_fsync_false outer = 0 - Minitar.unpack(Zlib::GzipReader.new(StringIO.new(TEST_TGZ)), "data__", [], :fsync => false) do |_label, _path, _stats| + Minitar.unpack(Zlib::GzipReader.new(StringIO.new(TEST_TGZ)), "data__", [], fsync: false) do |_label, _path, _stats| outer += 1 end - assert_equal(6, outer) + assert_equal 6, outer end end diff --git a/test/test_tar_output.rb b/test/test_tar_output.rb index 857b574..29ae1ff 100644 --- a/test/test_tar_output.rb +++ b/test/test_tar_output.rb @@ -34,7 +34,7 @@ def test_file_looks_good Dir.chdir("data__") do NAMES.each do |name| stat = File.stat(name) - opts = {:size => stat.size, :mode => 0o644} + opts = {size: stat.size, mode: 0o644} os.tar.add_file_simple(name, opts) do |ss| File.open(name, "rb") { |ff| ss.write(ff.read(4096)) until ff.eof? } end @@ -46,9 +46,9 @@ def test_file_looks_good names_from_tar = is.map do |entry| entry.name end - assert_equal(NAMES, names_from_tar) + assert_equal NAMES, names_from_tar end ensure - ff.close if ff + ff&.close end end diff --git a/test/test_tar_reader.rb b/test/test_tar_reader.rb index dcffe2a..df3e1b5 100644 --- a/test/test_tar_reader.rb +++ b/test/test_tar_reader.rb @@ -4,8 +4,6 @@ require "minitest_helper" class TestTarReader < Minitest::Test - include Archive::Tar::Minitar::ByteSize - def test_open_no_block str = tar_file_header("lib/foo", "", 0o10644, 10) + "\0" * 512 str += tar_file_header("bar", "baz", 0o644, 0) @@ -34,35 +32,35 @@ def test_multiple_entries Minitar::Reader.new(StringIO.new(str)) do |is| i = 0 is.each_entry do |entry| - assert_kind_of(Minitar::Reader::EntryStream, entry) - assert_equal(names[i], entry.name) - assert_equal(prefixes[i], entry.prefix) - assert_equal(sizes[i], entry.size) - assert_equal(modes[i], entry.mode) - assert_equal(isdir[i], entry.directory?) - assert_equal(isfile[i], entry.file?) + assert_kind_of Minitar::Reader::EntryStream, entry + assert_equal names[i], entry.name + assert_equal prefixes[i], entry.prefix + assert_equal sizes[i], entry.size + assert_equal modes[i], entry.mode + assert_equal isdir[i], entry.directory? + assert_equal isfile[i], entry.file? if prefixes[i] != "" - assert_equal(File.join(prefixes[i], names[i]), entry.full_name) + assert_equal File.join(prefixes[i], names[i]), entry.full_name else - assert_equal(names[i], entry.name) + assert_equal names[i], entry.name end i += 1 end - assert_equal(names.size, i) + assert_equal names.size, i end end def test_rewind_entry_works content = ("a".."z").to_a.join(" ") - str = tar_file_header("lib/foo", "", 0o10644, bytesize(content)) + - content + "\0" * (512 - bytesize(content)) + str = tar_file_header("lib/foo", "", 0o10644, content.bytesize) + + content + "\0" * (512 - content.bytesize) str << "\0" * 1024 Minitar::Reader.new(StringIO.new(str)) do |is| is.each_entry do |entry| 3.times do entry.rewind - assert_equal(content, entry.read) - assert_equal(bytesize(content), entry.pos) + assert_equal content, entry.read + assert_equal content.bytesize, entry.pos end end end @@ -70,53 +68,53 @@ def test_rewind_entry_works def test_rewind_works content = ("a".."z").to_a.join(" ") - str = tar_file_header("lib/foo", "", 0o10644, bytesize(content)) + - content + "\0" * (512 - bytesize(content)) + str = tar_file_header("lib/foo", "", 0o10644, content.bytesize) + + content + "\0" * (512 - content.bytesize) str << "\0" * 1024 Minitar::Reader.new(StringIO.new(str)) do |is| 3.times do is.rewind i = 0 is.each_entry do |entry| - assert_equal(content, entry.read) + assert_equal content, entry.read i += 1 end - assert_equal(1, i) + assert_equal 1, i end end end def test_read_works contents = ("a".."z").inject("") { |a, e| a << e * 100 } - str = tar_file_header("lib/foo", "", 0o10644, bytesize(contents)) + contents - str += "\0" * (512 - (bytesize(str) % 512)) + str = tar_file_header("lib/foo", "", 0o10644, contents.bytesize) + contents + str += "\0" * (512 - (str.bytesize % 512)) Minitar::Reader.new(StringIO.new(str)) do |is| is.each_entry do |entry| - assert_kind_of(Minitar::Reader::EntryStream, entry) - data = entry.read(3000) # bigger than bytesize(contents) - assert_equal(contents, data) - assert_equal(true, entry.eof?) + assert_kind_of Minitar::Reader::EntryStream, entry + data = entry.read(3000) # bigger than contents.bytesize + assert_equal contents, data + assert entry.eof? end end Minitar::Reader.new(StringIO.new(str)) do |is| is.each_entry do |entry| - assert_kind_of(Minitar::Reader::EntryStream, entry) + assert_kind_of Minitar::Reader::EntryStream, entry data = entry.read(100) - (entry.size - bytesize(data)).times { data << entry.getc.chr } - assert_equal(contents, data) - assert_equal(nil, entry.read(10)) - assert_equal(true, entry.eof?) + (entry.size - data.bytesize).times { data << entry.getc.chr } + assert_equal contents, data + assert_nil entry.read(10) + assert entry.eof? end end Minitar::Reader.new(StringIO.new(str)) do |is| is.each_entry do |entry| - assert_kind_of(Minitar::Reader::EntryStream, entry) + assert_kind_of Minitar::Reader::EntryStream, entry data = entry.read - assert_equal(contents, data) - assert_equal(nil, entry.read(10)) - assert_equal(nil, entry.read) - assert_equal(nil, entry.getc) - assert_equal(true, entry.eof?) + assert_equal contents, data + assert_nil entry.read(10) + assert_nil entry.read + assert_nil entry.getc + assert entry.eof? end end end @@ -125,25 +123,25 @@ def test_eof_works str = tar_file_header("bar", "baz", 0o644, 0) Minitar::Reader.new(StringIO.new(str)) do |is| is.each_entry do |entry| - assert_kind_of(Minitar::Reader::EntryStream, entry) + assert_kind_of Minitar::Reader::EntryStream, entry data = entry.read - assert_equal(nil, data) - assert_equal(nil, entry.read(10)) - assert_equal(nil, entry.read) - assert_equal(nil, entry.getc) - assert_equal(true, entry.eof?) + assert_nil data + assert_nil entry.read(10) + assert_nil entry.read + assert_nil entry.getc + assert entry.eof? end end str = tar_dir_header("foo", "bar", 0o12345) Minitar::Reader.new(StringIO.new(str)) do |is| is.each_entry do |entry| - assert_kind_of(Minitar::Reader::EntryStream, entry) + assert_kind_of Minitar::Reader::EntryStream, entry data = entry.read - assert_equal(nil, data) - assert_equal(nil, entry.read(10)) - assert_equal(nil, entry.read) - assert_equal(nil, entry.getc) - assert_equal(true, entry.eof?) + assert_nil data + assert_nil entry.read(10) + assert_nil entry.read + assert_nil entry.getc + assert entry.eof? end end str = tar_dir_header("foo", "bar", 0o12345) @@ -151,19 +149,19 @@ def test_eof_works str += tar_file_header("bar", "baz", 0o644, 0) Minitar::Reader.new(StringIO.new(str)) do |is| is.each_entry do |entry| - assert_kind_of(Minitar::Reader::EntryStream, entry) + assert_kind_of Minitar::Reader::EntryStream, entry data = entry.read - assert_equal(nil, data) - assert_equal(nil, entry.read(10)) - assert_equal(nil, entry.read) - assert_equal(nil, entry.getc) - assert_equal(true, entry.eof?) + assert_nil data + assert_nil entry.read(10) + assert_nil entry.read + assert_nil entry.getc + assert entry.eof? end end end def test_read_invalid_tar_file - assert_raises Archive::Tar::Minitar::InvalidTarStream do + assert_raises Minitar::InvalidTarStream do Minitar::Reader.open(StringIO.new("testing")) do |r| r.each_entry do |entry| fail "invalid tar file should not read files" diff --git a/test/test_tar_writer.rb b/test/test_tar_writer.rb index 4e20f05..2e33ed4 100644 --- a/test/test_tar_writer.rb +++ b/test/test_tar_writer.rb @@ -4,11 +4,7 @@ require "minitest_helper" class TestTarWriter < Minitest::Test - include Archive::Tar::Minitar::ByteSize - class DummyIO - include Archive::Tar::Minitar::ByteSize - attr_reader :data def initialize @@ -50,32 +46,32 @@ def test_add_file_simple @dummyos.reset Minitar::Writer.open(@dummyos) do |os| - os.add_file_simple("lib/foo/bar", :mode => 0o644, :size => 10) do |f| + os.add_file_simple("lib/foo/bar", mode: 0o644, size: 10) do |f| f.write "a" * 10 end - os.add_file_simple("lib/bar/baz", :mode => 0o644, :size => 100) do |f| + os.add_file_simple("lib/bar/baz", mode: 0o644, size: 100) do |f| f.write "fillme" end end assert_headers_equal(tar_file_header("lib/foo/bar", "", 0o644, 10), @dummyos.data[0, 512]) - assert_equal("a" * 10 + "\0" * 502, @dummyos.data[512, 512]) + assert_equal "a" * 10 + "\0" * 502, @dummyos.data[512, 512] assert_headers_equal(tar_file_header("lib/bar/baz", "", 0o644, 100), @dummyos.data[512 * 2, 512]) - assert_equal("fillme" + "\0" * 506, @dummyos.data[512 * 3, 512]) - assert_equal("\0" * 512, @dummyos.data[512 * 4, 512]) - assert_equal("\0" * 512, @dummyos.data[512 * 5, 512]) + assert_equal "fillme" + "\0" * 506, @dummyos.data[512 * 3, 512] + assert_equal "\0" * 512, @dummyos.data[512 * 4, 512] + assert_equal "\0" * 512, @dummyos.data[512 * 5, 512] end def test_write_operations_fail_after_closed @dummyos.reset - @os.add_file_simple("sadd", :mode => 0o644, :size => 20) { |f| } + @os.add_file_simple("sadd", mode: 0o644, size: 20) { |f| } @os.close assert_raises(Minitar::ClosedStream) { @os.flush } - assert_raises(Minitar::ClosedStream) { @os.add_file("dfdsf", :mode => 0o644) {} } - assert_raises(Minitar::ClosedStream) { @os.mkdir "sdfdsf", :mode => 0o644 } - assert_raises(Minitar::ClosedStream) { @os.symlink "a", "b", :mode => 0o644 } + assert_raises(Minitar::ClosedStream) { @os.add_file("dfdsf", mode: 0o644) {} } + assert_raises(Minitar::ClosedStream) { @os.mkdir "sdfdsf", mode: 0o644 } + assert_raises(Minitar::ClosedStream) { @os.symlink "a", "b", mode: 0o644 } end def test_file_name_is_split_correctly @@ -103,7 +99,7 @@ def test_file_name_is_split_correctly "#{"a" * 49}x" ] names.each do |name| - @os.add_file_simple(name, :mode => 0o644, :size => 10) {} + @os.add_file_simple(name, mode: 0o644, size: 10) {} end names.each_index do |i| assert_headers_equal( @@ -117,13 +113,13 @@ def test_file_name_is_long @dummyos.reset @os.add_file_simple(File.join("a" * 152, "b" * 10, "c" * 92), - :mode => 0o644, :size => 10) {} + mode: 0o644, size: 10) {} @os.add_file_simple(File.join("d" * 162, "e" * 10), - :mode => 0o644, :size => 10) {} + mode: 0o644, size: 10) {} @os.add_file_simple(File.join("f" * 10, "g" * 110), - :mode => 0o644, :size => 10) {} + mode: 0o644, size: 10) {} # Issue #6. - @os.add_file_simple("a" * 114, :mode => 0o644, :size => 10) {} + @os.add_file_simple("a" * 114, mode: 0o644, size: 10) {} # "././@LongLink", a file name, its actual header, its data, ... 4.times do |i| @@ -137,7 +133,7 @@ def test_add_file_simple_content_with_long_name long_name_file_content = "where_is_all_the_content_gone" - @os.add_file_simple("a" * 114, :mode => 0o0644, :data => long_name_file_content) + @os.add_file_simple("a" * 114, mode: 0o0644, data: long_name_file_content) assert_equal(long_name_file_content, @dummyos.data[3 * 512, long_name_file_content.bytesize]) @@ -156,7 +152,7 @@ def dummyos.respond_to_missing?(meth, all) long_name_file_content = "where_is_all_the_content_gone" Minitar::Writer.open(dummyos) do |os| - os.add_file("a" * 114, :mode => 0o0644) do |f| + os.add_file("a" * 114, mode: 0o0644) do |f| f.write(long_name_file_content) end end @@ -177,58 +173,58 @@ def dummyos.respond_to_missing?(meth, all) content1 = ("a".."z").to_a.join("") # 26 content2 = ("aa".."zz").to_a.join("") # 1352 Minitar::Writer.open(dummyos) do |os| - os.add_file("lib/foo/bar", :mode => 0o644) { |f, _opts| f.write "a" * 10 } - os.add_file("lib/bar/baz", :mode => 0o644) { |f, _opts| f.write content1 } - os.add_file("lib/bar/baz", :mode => 0o644) { |f, _opts| f.write content2 } - os.add_file("lib/bar/baz", :mode => 0o644) { |_f, _opts| } + os.add_file("lib/foo/bar", mode: 0o644) { |f, _opts| f.write "a" * 10 } + os.add_file("lib/bar/baz", mode: 0o644) { |f, _opts| f.write content1 } + os.add_file("lib/bar/baz", mode: 0o644) { |f, _opts| f.write content2 } + os.add_file("lib/bar/baz", mode: 0o644) { |_f, _opts| } end assert_headers_equal(tar_file_header("lib/foo/bar", "", 0o644, 10), dummyos[0, 512]) - assert_equal(%(#{"a" * 10}#{"\0" * 502}), dummyos[512, 512]) + assert_equal %(#{"a" * 10}#{"\0" * 502}), dummyos[512, 512] offset = 512 * 2 [content1, content2, ""].each do |data| assert_headers_equal(tar_file_header("lib/bar/baz", "", 0o644, - bytesize(data)), dummyos[offset, 512]) + data.bytesize), dummyos[offset, 512]) offset += 512 until !data || data == "" chunk = data[0, 512] data[0, 512] = "" - assert_equal(chunk + "\0" * (512 - bytesize(chunk)), + assert_equal(chunk + "\0" * (512 - chunk.bytesize), dummyos[offset, 512]) offset += 512 end end - assert_equal("\0" * 1024, dummyos[offset, 1024]) + assert_equal "\0" * 1024, dummyos[offset, 1024] end def test_add_file_tests_seekability - assert_raises(Archive::Tar::Minitar::NonSeekableStream) do - @os.add_file("libdfdsfd", :mode => 0o644) { |f| } + assert_raises(Minitar::NonSeekableStream) do + @os.add_file("libdfdsfd", mode: 0o644) { |f| } end end def test_write_header @dummyos.reset - @os.add_file_simple("lib/foo/bar", :mode => 0o644, :size => 0) {} + @os.add_file_simple("lib/foo/bar", mode: 0o644, size: 0) {} @os.flush assert_headers_equal(tar_file_header("lib/foo/bar", "", 0o644, 0), @dummyos.data[0, 512]) @dummyos.reset - @os.mkdir("lib/foo", :mode => 0o644) + @os.mkdir("lib/foo", mode: 0o644) assert_headers_equal(tar_dir_header("lib/foo", "", 0o644), @dummyos.data[0, 512]) - @os.mkdir("lib/bar", :mode => 0o644) + @os.mkdir("lib/bar", mode: 0o644) assert_headers_equal(tar_dir_header("lib/bar", "", 0o644), @dummyos.data[512 * 1, 512]) end def test_write_data @dummyos.reset - @os.add_file_simple("lib/foo/bar", :mode => 0o644, :size => 10) do |f| + @os.add_file_simple("lib/foo/bar", mode: 0o644, size: 10) do |f| f.write @data end @os.flush - assert_equal(@data + ("\0" * (512 - bytesize(@data))), + assert_equal(@data + ("\0" * (512 - @data.bytesize)), @dummyos.data[512, 512]) end @@ -243,27 +239,27 @@ def test_write_unicode_data file = ["lib/foo/b", 0xc3.chr, 0xa5.chr, "r"].join - @os.add_file_simple(file, :mode => 0o644, :size => 20) do |f| + @os.add_file_simple(file, mode: 0o644, size: 20) do |f| f.write @unicode end @os.flush - assert_equal(@unicode + ("\0" * (512 - bytesize(@unicode))), + assert_equal(@unicode + ("\0" * (512 - @unicode.bytesize)), @dummyos.data[512, 512]) end def test_file_size_is_checked @dummyos.reset assert_raises(Minitar::Writer::WriteBoundaryOverflow) do - @os.add_file_simple("lib/foo/bar", :mode => 0o644, :size => 10) do |f| + @os.add_file_simple("lib/foo/bar", mode: 0o644, size: 10) do |f| f.write "1" * 100 end end - @os.add_file_simple("lib/foo/bar", :mode => 0o644, :size => 10) { |f| } + @os.add_file_simple("lib/foo/bar", mode: 0o644, size: 10) { |f| } end def test_symlink @dummyos.reset - @os.symlink("lib/foo/bar", "lib/foo/baz", :mode => 0o644) + @os.symlink("lib/foo/bar", "lib/foo/baz", mode: 0o644) @os.flush assert_headers_equal(tar_symlink_header("lib/foo/bar", "", 0o644, "lib/foo/baz"), @dummyos.data[0, 512])