Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ronin compress command #184

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions lib/ronin/cli/commands/compress.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# frozen_string_literal: true
#
# Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# Ronin is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ronin is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ronin. If not, see <https://www.gnu.org/licenses/>.
#

require 'ronin/cli/string_methods_command'
require 'ronin/support/compression'
require 'ronin/support/archive'

module Ronin
class CLI
module Commands
#
# Compress the files.
#
# ## Usage
#
# ronin compress [option] [FILE ...]
#
# ## Options
#
# -f, --file FILE Optional file to process
# --string STRING Optional string to process
# -g, --gzip gzip compresses the data
# -z, --zlib zlib compress the data
# -n, --name Compressed file name
#
# ## Arguments
#
# [FILE ...] Optional file(s) to compress
#
class Compress < StringMethodsCommand
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we only want to support processing files, then probably want to inherit from FileProcessorCommand, and define a process_input(io) method to compress the input File or from stdin.

If we do want to support accepting arbitrary Strings (ex: ronin compress --string "hello world"), then we should inherit from StringProcessorCommand.

StringMethodsCommand is more meant to be used by commands which simply map options to certain monkey-patch methods that are added to String (ex: ronin decode --base64 -> String#base64_decode).

usage '[options] [FILE ...]'

option :gzip, short: '-g',
desc: 'gzip compresses the data' do
@compression_method = :gzip
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going to support --gzip, we should also support a --zlib option. ronin-support also happens to have a Ronin::Support::Compression::Zlib module we can use.


option :zlib, short: '-z',
desc: 'zlib compress the data' do
@compression_method = :zlib_deflate
end

option :name, short: '-n',
value: {
type: String
},
desc: 'compressed file name' do |name|
@compressed_file_name = name
end
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add an option for specifying compressed file name.


description 'Compress the data'

man_page 'ronin-compress.1'

#
# The compression format.
#
# @return [:gzip, :zlib_deflate]
#
attr_reader :compression_method

#
# Initializes the `ronin compress` command.
#
# @param [Hash{Symbol => Object}] kwargs
# Additional keyword arguments.
#
def initialize(**kwargs)
postmodern marked this conversation as resolved.
Show resolved Hide resolved
super(**kwargs)

@compression_method = :gzip
end

#
# Runs the `compress` sub-command.
#
# @param [Array<String>] files
# File arguments.
#
def run(*files)
if files.empty?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be unless files.empty? or if !files.empty??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about it like if there is file given as an argument, compress it, otherwise go to StringProcessorCommand#run else statement and process @input_values.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh.. now that I read it, it works different than i thought 😅

Copy link
Member

@postmodern postmodern Jan 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm maybe it might be easier to just write all files and strings into a common Support::Compression::GZip output stream? Although, we would have to have different logic for the Compression::Zlib code.

Another idea would be to define separate process_file and process_string methods. The process_file method would read the File and write the gziped/zlib'ed output to a .gz or .zlib file. The process_string method would instead gzip/zlib compress a String and write it to stdout; maybe also support a common --output option?

super(*files)
else
raise "Files can be compressed using gzip only" if @compression_method != :gzip
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do a print_error and call exit(1) if invalid combinations of options or arguments are given.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could support compressing files using SupportCompression.zlib_deflate. Just read the entire file using File.read, compress the String, and write it back out using File.binwrite.


Ronin::Support::Compression.gzip(compressed_file_name) do |gz|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also why are there two Ronin::Support::Compression.gzip(compressed_file_name) blocks? One in run and another in process_file?

files.each do |file|
File.open(file, 'rb') do |f|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid single letter block arguments. Rename file to like path or file_path, and f to file or io.

while (chunk = f.readpartial(4096))
gz.write chunk
end
end
rescue EOFError
rescue IOError => e
puts "Error reading file: #{e.message}"
end
end
end
end

#
# Reads and processes file.
#
# @param [String] path
# The path to the file.
#
def process_file(file)
raise "Files can be compressed using gzip only" if @compression_method != :gzip
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we raise an error, or just print some message and return?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call print_error and exit(1), or call print_error and return if you think the error can be printed but the command should continue processing arguments.


Ronin::Support::Compression.gzip(compressed_file_name) do |gz|
File.open(file, 'rb') do |f|
while (chunk = f.readpartial(4096))
gz.write chunk
end
end
rescue EOFError
rescue IOError => e
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rubocop does not like empty rescue :/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, there should be no IO errors when reading the file. I suspect file.eof? should make the rescues unnecessary.

puts "Error reading file: #{e.message}"
end
end
postmodern marked this conversation as resolved.
Show resolved Hide resolved

#
# Compress the string.
#
# @param [String] string
# The input string.
#
# @return [String]
# The compressed string.
#
def process_string(string)
string.send(@compression_method)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I try to avoid calling the monkey-patched methods when possible. Might be better to do Support::Compression.send(@compression_method,string).

end

private

#
# The compressed file name.
#
# @return String
#
def compressed_file_name
@compressed_file_name || "ronin_compressed_#{Time.now.to_i}.gz"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd get rid of the timestamp, or maybe add an option for it. I think a ronin compress command should behave exactly like gzip and just append the .gz extension on the given file name.

end
end
end
end
end
Loading