excavate 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c9ac901b1d15ed4e916d2e76c0b902070918d518169f177fdf338df564a002e4
4
+ data.tar.gz: f3d5f8d33256c59bd63d3d517e66ab46eda8cf1e02f3d26a732add451e8d92a7
5
+ SHA512:
6
+ metadata.gz: 2e9dcda07eec36ef7c4dbcced2927e26ad2b4daff57f3544a79aeac7c5a4da4fb44fd3d16e5868ae17387614ea549838aa61565fd283d19036e6aa0e02f8e54a
7
+ data.tar.gz: 9fd35d2d181083bf78a5c4250fb9ee22ff925f8b9d5964243f2d1f810f90dc04183973c9ff0d1c7c778879512e70749a5757ac4b9d81029e2b5a56331ba35c12
@@ -0,0 +1,36 @@
1
+ name: release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ release:
10
+ runs-on: ubuntu-18.04
11
+ steps:
12
+ - uses: actions/checkout@v1
13
+
14
+ - uses: ruby/setup-ruby@v1
15
+ with:
16
+ ruby-version: '2.6'
17
+
18
+ - run: bundle config set path 'vendor/bundle'
19
+
20
+ - run: bundle install --jobs 4 --retry 3
21
+
22
+ - run: bundle exec rspec
23
+
24
+ - name: Publish to rubygems.org
25
+ env:
26
+ RUBYGEMS_API_KEY: ${{secrets.FONTIST_CI_RUBYGEMS_API_KEY}}
27
+ run: |
28
+ gem install gem-release
29
+ touch ~/.gem/credentials
30
+ cat > ~/.gem/credentials << EOF
31
+ ---
32
+ :rubygems_api_key: ${RUBYGEMS_API_KEY}
33
+ EOF
34
+ chmod 0600 ~/.gem/credentials
35
+ git status
36
+ gem release
@@ -0,0 +1,47 @@
1
+ name: rspec
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+
8
+ jobs:
9
+ build:
10
+ name: Test on Ruby ${{ matrix.ruby }} ${{ matrix.os }}
11
+ runs-on: ${{ matrix.os }}
12
+ continue-on-error: ${{ matrix.experimental }}
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ ruby: [ '2.4', '2.5', '2.6', '2.7' ]
17
+ os: [ ubuntu-latest, windows-latest, macos-latest ]
18
+ experimental: [ false ]
19
+ include:
20
+ - ruby: '3.0'
21
+ os: 'ubuntu-latest'
22
+ experimental: true
23
+ - ruby: '3.0'
24
+ os: 'windows-latest'
25
+ experimental: true
26
+ - ruby: '3.0'
27
+ os: 'macos-latest'
28
+ experimental: true
29
+
30
+ steps:
31
+ - uses: actions/checkout@master
32
+
33
+ - uses: ruby/setup-ruby@v1
34
+ with:
35
+ ruby-version: ${{ matrix.ruby }}
36
+
37
+ - uses: actions/cache@v1
38
+ with:
39
+ path: vendor/bundle
40
+ key: bundle-${{ matrix.os }}-${{ matrix.ruby }}-${{ hashFiles('**/*.gemspec') }}
41
+ restore-keys: bundle-${{ matrix.os }}-${{ matrix.ruby }}
42
+
43
+ - run: bundle config set path 'vendor/bundle'
44
+
45
+ - run: bundle install --jobs 4 --retry 3
46
+
47
+ - run: bundle exec rspec
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ Gemfile.lock
10
+ .rubocop-*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ inherit_from:
2
+ - 'https://raw.githubusercontent.com/fontist/oss-guides/master/ci/rubocop.yml'
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ gem "gem-release"
8
+
9
+ gem "rake", "~> 13.0"
10
+
11
+ gem "rspec", "~> 3.2"
12
+ gem "rubocop", "0.75.0"
13
+ gem "rubocop-performance"
14
+ gem "rubocop-rails"
data/LICENSE.adoc ADDED
@@ -0,0 +1,13 @@
1
+ = BSD 3-Clause License
2
+
3
+ Copyright (c) 2020 Ribose Inc. All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
10
+
11
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.adoc ADDED
@@ -0,0 +1,102 @@
1
+ = Excavate: Ruby gem to extract nested archives
2
+
3
+ Extract nested archives with a single command.
4
+
5
+
6
+ == Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ [source,ruby]
11
+ ----
12
+ gem "excavate"
13
+ ----
14
+
15
+ And then execute:
16
+
17
+ [source,sh]
18
+ ----
19
+ $ bundle install
20
+ ----
21
+
22
+ Or install it yourself as:
23
+
24
+ [source,sh]
25
+ ----
26
+ $ gem install excavate
27
+ ----
28
+
29
+
30
+ == Usage
31
+
32
+ To extract an archive containing other archives inside:
33
+
34
+ [source,ruby]
35
+ ----
36
+ archive = "path/to/archive.cab"
37
+ target = Dir.mktmpdir
38
+ Excavate::Archive.new(archive).extract(target, recursive_packages: true)
39
+ ----
40
+
41
+ The same but allowing to choose only necessary files inside:
42
+
43
+ [source,ruby]
44
+ ----
45
+ Excavate::Archive.new("path/to/archive.cab").files(recursive_packages: true) do |path|
46
+ FileUtils.mv(path, Dir.mktmpdir) if path.end_with?(".txt")
47
+ end
48
+ ----
49
+
50
+
51
+ == Development
52
+
53
+ We are following Sandi Metz's Rules for this gem, you can read the
54
+ http://robots.thoughtbot.com/post/50655960596/sandi-metz-rules-for-developers[description of the rules here].
55
+ All new code should follow these
56
+ rules. If you make changes in a pre-existing file that violates these rules you
57
+ should fix the violations as part of your contribution.
58
+
59
+
60
+ == Releasing
61
+
62
+ Releasing is done automatically with GitHub Action. Just bump and tag with `gem-release`.
63
+
64
+ For a patch release (0.0.x) use:
65
+
66
+ [source,ruby]
67
+ ----
68
+ gem bump --version patch --tag --push
69
+ ----
70
+
71
+ For a minor release (0.x.0) use:
72
+
73
+ [source,ruby]
74
+ ----
75
+ gem bump --version minor --tag --push
76
+ ----
77
+
78
+
79
+ == Contributing
80
+
81
+ First, thank you for contributing! We love pull requests from everyone. By
82
+ participating in this project, you hereby grant https://www.ribose.com[Ribose Inc.] the
83
+ right to grant or transfer an unlimited number of non exclusive licenses or
84
+ sub-licenses to third parties, under the copyright covering the contribution
85
+ to use the contribution by all means.
86
+
87
+ Here are a few technical guidelines to follow:
88
+
89
+ 1. Open an https://github.com/fontist/excavate/issues[issue] to discuss a new feature.
90
+ 1. Write tests to support your new feature.
91
+ 1. Make sure the entire test suite passes locally and on CI.
92
+ 1. Open a Pull Request.
93
+ 1. https://github.com/thoughtbot/guides/tree/master/protocol/git#write-a-feature[Squash your commits]
94
+ after receiving feedback.
95
+ 1. Party!
96
+
97
+
98
+ == License
99
+
100
+ This gem is distributed with a BSD 3-Clause license.
101
+
102
+ This gem is developed, maintained and funded by https://www.ribose.com/[Ribose Inc.]
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
data/excavate.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/excavate/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "excavate"
7
+ spec.version = Excavate::VERSION
8
+ spec.authors = ["Ribose Inc."]
9
+ spec.email = ["open.source@ribose.com"]
10
+
11
+ spec.summary = "Extract nested archives with a single command."
12
+ spec.description = "Extract nested archives with a single command."
13
+ spec.homepage = "https://github.com/fontist/excavate"
14
+ spec.license = "BSD-3-Clause"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/fontist/excavate"
18
+ spec.metadata["changelog_uri"] = "https://github.com/fontist/excavate"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features|bin)/}) }
24
+ end
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+
29
+ spec.add_runtime_dependency "arr-pm", "~> 0.0"
30
+ spec.add_runtime_dependency "libmspack", "~> 0.1"
31
+ spec.add_runtime_dependency "ruby-ole", "~> 1.0"
32
+ spec.add_runtime_dependency "rubyzip", "~> 2.3"
33
+ spec.add_runtime_dependency "seven_zip_ruby", "~> 1.0"
34
+ end
data/lib/excavate.rb ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "excavate/version"
4
+ require_relative "excavate/extractors"
5
+ require_relative "excavate/archive"
6
+ require_relative "excavate/file_magic"
7
+ require_relative "excavate/utils"
8
+
9
+ module Excavate
10
+ class Error < StandardError; end
11
+ class UnknownArchiveError < Error; end
12
+ end
@@ -0,0 +1,96 @@
1
+ module Excavate
2
+ class Archive
3
+ TYPES = { "cab" => Extractors::CabExtractor,
4
+ "cpio" => Extractors::CpioExtractor,
5
+ "exe" => Extractors::SevenZipExtractor,
6
+ "gz" => Extractors::GzipExtractor,
7
+ "msi" => Extractors::OleExtractor,
8
+ "rpm" => Extractors::RpmExtractor,
9
+ "tar" => Extractors::TarExtractor,
10
+ "zip" => Extractors::ZipExtractor }.freeze
11
+
12
+ def initialize(archive)
13
+ @archive = archive
14
+ end
15
+
16
+ def files(recursive_packages: false)
17
+ target = Dir.mktmpdir
18
+ extract(target, recursive_packages: recursive_packages)
19
+
20
+ all_files_in(target).map do |file|
21
+ yield file
22
+ end
23
+ ensure
24
+ FileUtils.rm_rf(target)
25
+ end
26
+
27
+ def extract(target, recursive_packages: false)
28
+ if recursive_packages
29
+ extract_recursively(@archive, target)
30
+ else
31
+ extract_once(@archive, target)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def extract_recursively(archive, target)
38
+ extract_once(archive, target)
39
+
40
+ all_files_in(target).each do |file|
41
+ next unless archive?(file)
42
+
43
+ extract_and_replace(file)
44
+ end
45
+ end
46
+
47
+ def extract_once(archive, target)
48
+ extension = normalized_extension(archive)
49
+ extractor_class = TYPES[extension]
50
+ raise(UnknownArchiveError, "Could not unarchive `#{archive}`.") unless extractor_class
51
+
52
+ extractor_class.new(archive).extract(target)
53
+ rescue StandardError => e
54
+ raise unless extension == "exe" && e.message.start_with?("Invalid file format")
55
+
56
+ Extractors::CabExtractor.new(archive).extract(target)
57
+ end
58
+
59
+ def extract_and_replace(archive)
60
+ target = Dir.mktmpdir
61
+ extract_recursively(archive, target)
62
+
63
+ FileUtils.rm(archive)
64
+ FileUtils.mv(target, archive)
65
+ rescue FFI::NullPointerError => e
66
+ FileUtils.rmdir(target)
67
+ raise unless normalized_extension(archive) == "exe" &&
68
+ e.message.start_with?("invalid memory read at address=0x0000000000000000")
69
+ end
70
+
71
+ def normalized_extension(file)
72
+ fetch_extension(file).downcase
73
+ end
74
+
75
+ def fetch_extension(file)
76
+ File.extname(filename(file)).sub(/^\./, "")
77
+ end
78
+
79
+ def filename(file)
80
+ if file.respond_to?(:original_filename)
81
+ file.original_filename
82
+ else
83
+ File.basename(file)
84
+ end
85
+ end
86
+
87
+ def all_files_in(dir)
88
+ Dir.glob(File.join(dir, "**", "*"))
89
+ end
90
+
91
+ def archive?(file)
92
+ ext = normalized_extension(file)
93
+ TYPES.key?(ext)
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "extractors/extractor"
2
+ require_relative "extractors/cab_extractor"
3
+ require_relative "extractors/cpio_extractor"
4
+ require_relative "extractors/gzip_extractor"
5
+ require_relative "extractors/ole_extractor"
6
+ require_relative "extractors/rpm_extractor"
7
+ require_relative "extractors/seven_zip_extractor"
8
+ require_relative "extractors/tar_extractor"
9
+ require_relative "extractors/zip_extractor"
@@ -0,0 +1,33 @@
1
+ require "libmspack"
2
+
3
+ module Excavate
4
+ module Extractors
5
+ class CabExtractor < Extractor
6
+ def extract(target)
7
+ open_cab(@archive) do |decompressor, cab|
8
+ file = cab.files
9
+
10
+ while file
11
+ path = File.join(target, file.filename)
12
+ decompressor.extract(file, path)
13
+ file = file.next
14
+ end
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def open_cab(archive)
21
+ decompressor = LibMsPack::CabDecompressor.new
22
+ cab = Utils.silence_stream(STDERR) do
23
+ decompressor.search(archive)
24
+ end
25
+
26
+ yield decompressor, cab
27
+
28
+ decompressor.close(cab)
29
+ decompressor.destroy
30
+ end
31
+ end
32
+ end
33
+ end