excavate 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/release.yml +36 -0
- data/.github/workflows/rspec.yml +47 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/Gemfile +14 -0
- data/LICENSE.adoc +13 -0
- data/README.adoc +102 -0
- data/Rakefile +4 -0
- data/excavate.gemspec +34 -0
- data/lib/excavate.rb +12 -0
- data/lib/excavate/archive.rb +96 -0
- data/lib/excavate/extractors.rb +9 -0
- data/lib/excavate/extractors/cab_extractor.rb +33 -0
- data/lib/excavate/extractors/cpio/cpio.rb +199 -0
- data/lib/excavate/extractors/cpio/cpio_old_format.rb +436 -0
- data/lib/excavate/extractors/cpio_extractor.rb +44 -0
- data/lib/excavate/extractors/extractor.rb +13 -0
- data/lib/excavate/extractors/gzip_extractor.rb +13 -0
- data/lib/excavate/extractors/ole_extractor.rb +66 -0
- data/lib/excavate/extractors/rpm_extractor.rb +33 -0
- data/lib/excavate/extractors/seven_zip_extractor.rb +13 -0
- data/lib/excavate/extractors/tar_extractor.rb +29 -0
- data/lib/excavate/extractors/zip_extractor.rb +17 -0
- data/lib/excavate/file_magic.rb +18 -0
- data/lib/excavate/utils.rb +14 -0
- data/lib/excavate/version.rb +5 -0
- metadata +143 -0
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
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
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
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
|